Compare commits
No commits in common. "main" and "feat/dataviz-users" have entirely different histories.
main
...
feat/datav
133
i18n/en/msg.txt
133
i18n/en/msg.txt
@ -1,133 +0,0 @@
|
|||||||
用户管理: User Management
|
|
||||||
用户名: Username
|
|
||||||
密码: Password
|
|
||||||
确认密码: Confirm Password
|
|
||||||
姓名: Full Name
|
|
||||||
邮箱: Email
|
|
||||||
手机号: Phone Number
|
|
||||||
所属机构: Organization
|
|
||||||
角色: Role
|
|
||||||
状态: Status
|
|
||||||
启用: Enable
|
|
||||||
停用: Disable
|
|
||||||
新增用户: Add User
|
|
||||||
编辑用户: Edit User
|
|
||||||
删除用户: Delete User
|
|
||||||
重置密码: Reset Password
|
|
||||||
角色管理: Role Management
|
|
||||||
角色名称: Role Name
|
|
||||||
角色编码: Role Code
|
|
||||||
角色描述: Role Description
|
|
||||||
权限: Permission
|
|
||||||
新增角色: Add Role
|
|
||||||
编辑角色: Edit Role
|
|
||||||
删除角色: Delete Role
|
|
||||||
分配权限: Assign Permission
|
|
||||||
机构管理: Organization Management
|
|
||||||
机构名称: Organization Name
|
|
||||||
机构编码: Organization Code
|
|
||||||
机构类型: Organization Type
|
|
||||||
上级机构: Parent Organization
|
|
||||||
新增机构: Add Organization
|
|
||||||
编辑机构: Edit Organization
|
|
||||||
删除机构: Delete Organization
|
|
||||||
菜单管理: Menu Management
|
|
||||||
菜单名称: Menu Name
|
|
||||||
菜单路径: Menu Path
|
|
||||||
菜单图标: Menu Icon
|
|
||||||
菜单排序: Menu Sort Order
|
|
||||||
上级菜单: Parent Menu
|
|
||||||
新增菜单: Add Menu
|
|
||||||
编辑菜单: Edit Menu
|
|
||||||
删除菜单: Delete Menu
|
|
||||||
权限管理: Permission Management
|
|
||||||
权限名称: Permission Name
|
|
||||||
权限编码: Permission Code
|
|
||||||
权限类型: Permission Type
|
|
||||||
资源权限: Resource Permission
|
|
||||||
操作权限: Operation Permission
|
|
||||||
数据权限: Data Permission
|
|
||||||
登录日志: Login Log
|
|
||||||
登录时间: Login Time
|
|
||||||
登录IP: Login IP
|
|
||||||
登录状态: Login Status
|
|
||||||
成功: Success
|
|
||||||
失败: Failed
|
|
||||||
操作日志: Operation Log
|
|
||||||
操作类型: Operation Type
|
|
||||||
操作时间: Operation Time
|
|
||||||
操作人: Operator
|
|
||||||
操作结果: Operation Result
|
|
||||||
API密钥管理: API Key Management
|
|
||||||
密钥名称: Key Name
|
|
||||||
密钥值: Key Value
|
|
||||||
密钥状态: Key Status
|
|
||||||
新增密钥: Add Key
|
|
||||||
编辑密钥: Edit Key
|
|
||||||
删除密钥: Delete Key
|
|
||||||
在线用户: Online Users
|
|
||||||
会话ID: Session ID
|
|
||||||
最后活动时间: Last Activity Time
|
|
||||||
强制下线: Force Logout
|
|
||||||
安全设置: Security Settings
|
|
||||||
密码策略: Password Policy
|
|
||||||
密码长度: Password Length
|
|
||||||
密码复杂度: Password Complexity
|
|
||||||
密码有效期: Password Expiry
|
|
||||||
登录失败锁定: Login Failure Lock
|
|
||||||
最大失败次数: Max Failed Attempts
|
|
||||||
锁定时长: Lock Duration
|
|
||||||
双因素认证: Two-Factor Authentication
|
|
||||||
启用双因素: Enable 2FA
|
|
||||||
验证码: Verification Code
|
|
||||||
Token管理: Token Management
|
|
||||||
Token名称: Token Name
|
|
||||||
Token过期时间: Token Expiry Time
|
|
||||||
新增Token: Add Token
|
|
||||||
编辑Token: Edit Token
|
|
||||||
删除Token: Delete Token
|
|
||||||
刷新Token: Refresh Token
|
|
||||||
用户组: User Group
|
|
||||||
组名称: Group Name
|
|
||||||
组描述: Group Description
|
|
||||||
新增用户组: Add User Group
|
|
||||||
编辑用户组: Edit User Group
|
|
||||||
删除用户组: Delete User Group
|
|
||||||
成员管理: Member Management
|
|
||||||
添加成员: Add Member
|
|
||||||
移除成员: Remove Member
|
|
||||||
搜索: Search
|
|
||||||
名称或编码: Name or Code
|
|
||||||
操作: Action
|
|
||||||
描述: Description
|
|
||||||
类型: Type
|
|
||||||
名称: Name
|
|
||||||
编码: Code
|
|
||||||
备注: Remarks
|
|
||||||
创建时间: Created Time
|
|
||||||
更新时间: Updated Time
|
|
||||||
新增: Add
|
|
||||||
保存: Save
|
|
||||||
取消: Cancel
|
|
||||||
确认: Confirm
|
|
||||||
删除: Delete
|
|
||||||
编辑: Edit
|
|
||||||
查看: View
|
|
||||||
导出: Export
|
|
||||||
打印: Print
|
|
||||||
刷新: Refresh
|
|
||||||
返回: Back
|
|
||||||
提交: Submit
|
|
||||||
重置: Reset
|
|
||||||
Conform: Conform
|
|
||||||
Discard: Discard
|
|
||||||
Submit: Submit
|
|
||||||
Reset: Reset
|
|
||||||
Cancel: Cancel
|
|
||||||
全部: All
|
|
||||||
日期: Date
|
|
||||||
管理员: Administrator
|
|
||||||
普通用户: Normal User
|
|
||||||
只读用户: Read-Only User
|
|
||||||
访问控制: Access Control
|
|
||||||
最后登录: Last Login
|
|
||||||
133
i18n/jp/msg.txt
133
i18n/jp/msg.txt
@ -1,133 +0,0 @@
|
|||||||
用户管理: ユーザー管理
|
|
||||||
用户名: ユーザー名
|
|
||||||
密码: パスワード
|
|
||||||
确认密码: パスワード確認
|
|
||||||
姓名: 氏名
|
|
||||||
邮箱: メールアドレス
|
|
||||||
手机号: 電話番号
|
|
||||||
所属机构: 所属組織
|
|
||||||
角色: ロール
|
|
||||||
状态: ステータス
|
|
||||||
启用: 有効
|
|
||||||
停用: 無効
|
|
||||||
新增用户: ユーザー追加
|
|
||||||
编辑用户: ユーザー編集
|
|
||||||
删除用户: ユーザー削除
|
|
||||||
重置密码: パスワードリセット
|
|
||||||
角色管理: ロール管理
|
|
||||||
角色名称: ロール名
|
|
||||||
角色编码: ロールコード
|
|
||||||
角色描述: ロール説明
|
|
||||||
权限: 権限
|
|
||||||
新增角色: ロール追加
|
|
||||||
编辑角色: ロール編集
|
|
||||||
删除角色: ロール削除
|
|
||||||
分配权限: 権限割り当て
|
|
||||||
机构管理: 組織管理
|
|
||||||
机构名称: 組織名
|
|
||||||
机构编码: 組織コード
|
|
||||||
机构类型: 組織タイプ
|
|
||||||
上级机构: 上位組織
|
|
||||||
新增机构: 組織追加
|
|
||||||
编辑机构: 組織編集
|
|
||||||
删除机构: 組織削除
|
|
||||||
菜单管理: メニュー管理
|
|
||||||
菜单名称: メニュー名
|
|
||||||
菜单路径: メニューパス
|
|
||||||
菜单图标: メニューアイコン
|
|
||||||
菜单排序: メニュー並び順
|
|
||||||
上级菜单: 上位メニュー
|
|
||||||
新增菜单: メニュー追加
|
|
||||||
编辑菜单: メニュー編集
|
|
||||||
删除菜单: メニュー削除
|
|
||||||
权限管理: 権限管理
|
|
||||||
权限名称: 権限名
|
|
||||||
权限编码: 権限コード
|
|
||||||
权限类型: 権限タイプ
|
|
||||||
资源权限: リソース権限
|
|
||||||
操作权限: 操作権限
|
|
||||||
数据权限: データ権限
|
|
||||||
登录日志: ログインログ
|
|
||||||
登录时间: ログイン時間
|
|
||||||
登录IP: ログインIP
|
|
||||||
登录状态: ログインステータス
|
|
||||||
成功: 成功
|
|
||||||
失败: 失敗
|
|
||||||
操作日志: 操作ログ
|
|
||||||
操作类型: 操作タイプ
|
|
||||||
操作时间: 操作時間
|
|
||||||
操作人: 操作者
|
|
||||||
操作结果: 操作結果
|
|
||||||
API密钥管理: APIキー管理
|
|
||||||
密钥名称: キー名
|
|
||||||
密钥值: キー値
|
|
||||||
密钥状态: キーステータス
|
|
||||||
新增密钥: キー追加
|
|
||||||
编辑密钥: キー編集
|
|
||||||
删除密钥: キー削除
|
|
||||||
在线用户: オンラインユーザー
|
|
||||||
会话ID: セッションID
|
|
||||||
最后活动时间: 最終アクティビティ時間
|
|
||||||
强制下线: 強制ログアウト
|
|
||||||
安全设置: セキュリティ設定
|
|
||||||
密码策略: パスワードポリシー
|
|
||||||
密码长度: パスワード長さ
|
|
||||||
密码复杂度: パスワード複雑度
|
|
||||||
密码有效期: パスワード有効期限
|
|
||||||
登录失败锁定: ログイン失敗ロック
|
|
||||||
最大失败次数: 最大失敗回数
|
|
||||||
锁定时长: ロック期間
|
|
||||||
双因素认证: 二要素認証
|
|
||||||
启用双因素: 二要素認証を有効化
|
|
||||||
验证码: 認証コード
|
|
||||||
Token管理: トークン管理
|
|
||||||
Token名称: トークン名
|
|
||||||
Token过期时间: トークン有効期限
|
|
||||||
新增Token: トークン追加
|
|
||||||
编辑Token: トークン編集
|
|
||||||
删除Token: トークン削除
|
|
||||||
刷新Token: トークン更新
|
|
||||||
用户组: ユーザーグループ
|
|
||||||
组名称: グループ名
|
|
||||||
组描述: グループ説明
|
|
||||||
新增用户组: ユーザーグループ追加
|
|
||||||
编辑用户组: ユーザーグループ編集
|
|
||||||
删除用户组: ユーザーグループ削除
|
|
||||||
成员管理: メンバー管理
|
|
||||||
添加成员: メンバー追加
|
|
||||||
移除成员: メンバー削除
|
|
||||||
搜索: 検索
|
|
||||||
名称或编码: 名前またはコード
|
|
||||||
操作: 操作
|
|
||||||
描述: 説明
|
|
||||||
类型: タイプ
|
|
||||||
名称: 名前
|
|
||||||
编码: コード
|
|
||||||
备注: 備考
|
|
||||||
创建时间: 作成日時
|
|
||||||
更新时间: 更新日時
|
|
||||||
新增: 追加
|
|
||||||
保存: 保存
|
|
||||||
取消: キャンセル
|
|
||||||
确认: 確認
|
|
||||||
删除: 削除
|
|
||||||
编辑: 編集
|
|
||||||
查看: 表示
|
|
||||||
导出: エクスポート
|
|
||||||
打印: 印刷
|
|
||||||
刷新: 更新
|
|
||||||
返回: 戻る
|
|
||||||
提交: 送信
|
|
||||||
重置: リセット
|
|
||||||
Conform: 確認
|
|
||||||
Discard: 破棄
|
|
||||||
Submit: 送信
|
|
||||||
Reset: リセット
|
|
||||||
Cancel: キャンセル
|
|
||||||
全部: すべて
|
|
||||||
日期: 日付
|
|
||||||
管理员: 管理者
|
|
||||||
普通用户: 一般ユーザー
|
|
||||||
只读用户: 読み取り専用ユーザー
|
|
||||||
访问控制: アクセス制御
|
|
||||||
最后登录: 最終ログイン
|
|
||||||
133
i18n/ko/msg.txt
133
i18n/ko/msg.txt
@ -1,133 +0,0 @@
|
|||||||
用户管理: 사용자 관리
|
|
||||||
用户名: 사용자명
|
|
||||||
密码: 비밀번호
|
|
||||||
确认密码: 비밀번호 확인
|
|
||||||
姓名: 이름
|
|
||||||
邮箱: 이메일
|
|
||||||
手机号: 전화번호
|
|
||||||
所属机构: 소속 기관
|
|
||||||
角色: 역할
|
|
||||||
状态: 상태
|
|
||||||
启用: 활성화
|
|
||||||
停用: 비활성화
|
|
||||||
新增用户: 사용자 추가
|
|
||||||
编辑用户: 사용자 편집
|
|
||||||
删除用户: 사용자 삭제
|
|
||||||
重置密码: 비밀번호 재설정
|
|
||||||
角色管理: 역할 관리
|
|
||||||
角色名称: 역할명
|
|
||||||
角色编码: 역할 코드
|
|
||||||
角色描述: 역할 설명
|
|
||||||
权限: 권한
|
|
||||||
新增角色: 역할 추가
|
|
||||||
编辑角色: 역할 편집
|
|
||||||
删除角色: 역할 삭제
|
|
||||||
分配权限: 권한 할당
|
|
||||||
机构管理: 기관 관리
|
|
||||||
机构名称: 기관명
|
|
||||||
机构编码: 기관 코드
|
|
||||||
机构类型: 기관 유형
|
|
||||||
上级机构: 상위 기관
|
|
||||||
新增机构: 기관 추가
|
|
||||||
编辑机构: 기관 편집
|
|
||||||
删除机构: 기관 삭제
|
|
||||||
菜单管理: 메뉴 관리
|
|
||||||
菜单名称: 메뉴명
|
|
||||||
菜单路径: 메뉴 경로
|
|
||||||
菜单图标: 메뉴 아이콘
|
|
||||||
菜单排序: 메뉴 정렬
|
|
||||||
上级菜单: 상위 메뉴
|
|
||||||
新增菜单: 메뉴 추가
|
|
||||||
编辑菜单: 메뉴 편집
|
|
||||||
删除菜单: 메뉴 삭제
|
|
||||||
权限管理: 권한 관리
|
|
||||||
权限名称: 권한명
|
|
||||||
权限编码: 권한 코드
|
|
||||||
权限类型: 권한 유형
|
|
||||||
资源权限: 리소스 권한
|
|
||||||
操作权限: 작업 권한
|
|
||||||
数据权限: 데이터 권한
|
|
||||||
登录日志: 로그인 로그
|
|
||||||
登录时间: 로그인 시간
|
|
||||||
登录IP: 로그인 IP
|
|
||||||
登录状态: 로그인 상태
|
|
||||||
成功: 성공
|
|
||||||
失败: 실패
|
|
||||||
操作日志: 작업 로그
|
|
||||||
操作类型: 작업 유형
|
|
||||||
操作时间: 작업 시간
|
|
||||||
操作人: 작업자
|
|
||||||
操作结果: 작업 결과
|
|
||||||
API密钥管理: API 키 관리
|
|
||||||
密钥名称: 키 이름
|
|
||||||
密钥值: 키 값
|
|
||||||
密钥状态: 키 상태
|
|
||||||
新增密钥: 키 추가
|
|
||||||
编辑密钥: 키 편집
|
|
||||||
删除密钥: 키 삭제
|
|
||||||
在线用户: 온라인 사용자
|
|
||||||
会话ID: 세션 ID
|
|
||||||
最后活动时间: 마지막 활동 시간
|
|
||||||
强制下线: 강제 로그아웃
|
|
||||||
安全设置: 보안 설정
|
|
||||||
密码策略: 비밀번호 정책
|
|
||||||
密码长度: 비밀번호 길이
|
|
||||||
密码复杂度: 비밀번호 복잡도
|
|
||||||
密码有效期: 비밀번호 유효기간
|
|
||||||
登录失败锁定: 로그인 실패 잠금
|
|
||||||
最大失败次数: 최대 실패 횟수
|
|
||||||
锁定时长: 잠금 시간
|
|
||||||
双因素认证: 이중 인증
|
|
||||||
启用双因素: 이중 인증 활성화
|
|
||||||
验证码: 인증 코드
|
|
||||||
Token管理: 토큰 관리
|
|
||||||
Token名称: 토큰 이름
|
|
||||||
Token过期时间: 토큰 만료 시간
|
|
||||||
新增Token: 토큰 추가
|
|
||||||
编辑Token: 토큰 편집
|
|
||||||
删除Token: 토큰 삭제
|
|
||||||
刷新Token: 토큰 갱신
|
|
||||||
用户组: 사용자 그룹
|
|
||||||
组名称: 그룹 이름
|
|
||||||
组描述: 그룹 설명
|
|
||||||
新增用户组: 사용자 그룹 추가
|
|
||||||
编辑用户组: 사용자 그룹 편집
|
|
||||||
删除用户组: 사용자 그룹 삭제
|
|
||||||
成员管理: 멤버 관리
|
|
||||||
添加成员: 멤버 추가
|
|
||||||
移除成员: 멤버 제거
|
|
||||||
搜索: 검색
|
|
||||||
名称或编码: 이름 또는 코드
|
|
||||||
操作: 작업
|
|
||||||
描述: 설명
|
|
||||||
类型: 유형
|
|
||||||
名称: 이름
|
|
||||||
编码: 코드
|
|
||||||
备注: 비고
|
|
||||||
创建时间: 생성 시간
|
|
||||||
更新时间: 업데이트 시간
|
|
||||||
新增: 추가
|
|
||||||
保存: 저장
|
|
||||||
取消: 취소
|
|
||||||
确认: 확인
|
|
||||||
删除: 삭제
|
|
||||||
编辑: 편집
|
|
||||||
查看: 보기
|
|
||||||
导出: 내보내기
|
|
||||||
打印: 인쇄
|
|
||||||
刷新: 새로고침
|
|
||||||
返回: 뒤로
|
|
||||||
提交: 제출
|
|
||||||
重置: 초기화
|
|
||||||
Conform: 확인
|
|
||||||
Discard: 폐기
|
|
||||||
Submit: 제출
|
|
||||||
Reset: 초기화
|
|
||||||
Cancel: 취소
|
|
||||||
全部: 전체
|
|
||||||
日期: 날짜
|
|
||||||
管理员: 관리자
|
|
||||||
普通用户: 일반 사용자
|
|
||||||
只读用户: 읽기 전용 사용자
|
|
||||||
访问控制: 접근 제어
|
|
||||||
最后登录: 마지막 로그인
|
|
||||||
139
i18n/zh/msg.txt
139
i18n/zh/msg.txt
@ -1,139 +0,0 @@
|
|||||||
Account Locked: Account Locked
|
|
||||||
Account locked due to too many failed login attempts. Please try again in 5 minutes.: Account locked due to too many failed login attempts. Please try again in 5 minutes.
|
|
||||||
Add Error: Add Error
|
|
||||||
Add Success: Add Success
|
|
||||||
Add reseller Error: Add reseller Error
|
|
||||||
Cancel: Cancel
|
|
||||||
Conform: Conform
|
|
||||||
Discard: Discard
|
|
||||||
Error: Error
|
|
||||||
Login: Login
|
|
||||||
Login Error: Login Error
|
|
||||||
Reset: Reset
|
|
||||||
Reset Password: Reset Password
|
|
||||||
Submit: Submit
|
|
||||||
Success: Success
|
|
||||||
apikey: apikey
|
|
||||||
dappid参数必填: dappid参数必填
|
|
||||||
failed: failed
|
|
||||||
id: id
|
|
||||||
id/orgid必填: id/orgid必填
|
|
||||||
logout success: logout success
|
|
||||||
no user selected: no user selected
|
|
||||||
ok: ok
|
|
||||||
system error: system error
|
|
||||||
user disabled: user disabled
|
|
||||||
user enabled: user enabled
|
|
||||||
user name or password error: user name or password error
|
|
||||||
user register: user register
|
|
||||||
user.id和user.orgid必填: user.id和user.orgid必填
|
|
||||||
users必填: users必填
|
|
||||||
you are not owner user: you are not owner user
|
|
||||||
角色: 角色
|
|
||||||
两次输入的密码不一致: 两次输入的密码不一致
|
|
||||||
个人信息已更新: 个人信息已更新
|
|
||||||
主营业务描述: 主营业务描述
|
|
||||||
保存成功: 保存成功
|
|
||||||
允许IP集: 允许IP集
|
|
||||||
刷新页面: 刷新页面
|
|
||||||
发送验证码: 发送验证码
|
|
||||||
发送验证码出错,请检查短信模板配置和百度API连接: 发送验证码出错,请检查短信模板配置和百度API连接
|
|
||||||
同步应用id: 同步应用id
|
|
||||||
名称: 名称
|
|
||||||
启用日期: 启用日期
|
|
||||||
图标: 图标
|
|
||||||
地址: 地址
|
|
||||||
增加管理员: 增加管理员
|
|
||||||
失效日期: 失效日期
|
|
||||||
完善个人信息: 完善个人信息
|
|
||||||
完善信息: 完善信息
|
|
||||||
审计日志: 审计日志
|
|
||||||
密码: 密码
|
|
||||||
密码登录: 密码登录
|
|
||||||
应用名称: 应用名称
|
|
||||||
我的角色: 我的角色
|
|
||||||
所在地区id: 所在地区id
|
|
||||||
所在城市id: 所在城市id
|
|
||||||
所在省id: 所在省id
|
|
||||||
所属机构: 所属机构
|
|
||||||
手机: 手机
|
|
||||||
手机号: 手机号
|
|
||||||
手机号需短信验证后方可注册: 手机号需短信验证后方可注册
|
|
||||||
手机登录: 手机登录
|
|
||||||
手机短信验证码出错: 手机短信验证码出错
|
|
||||||
扫描未授权文件: 扫描未授权文件
|
|
||||||
扫码: 扫码
|
|
||||||
描述: 描述
|
|
||||||
是否审计: 是否审计
|
|
||||||
显示名: 显示名
|
|
||||||
最后登录: 最后登录
|
|
||||||
最后登录失败时间: 最后登录失败时间
|
|
||||||
未注册的手机号将自动创建账号: 未注册的手机号将自动创建账号
|
|
||||||
机构: 机构
|
|
||||||
机构id: 机构id
|
|
||||||
机构别名: 机构别名
|
|
||||||
机构名称: 机构名称
|
|
||||||
机构拥有角色: 机构拥有角色
|
|
||||||
机构简称: 机构简称
|
|
||||||
机构类型: 机构类型
|
|
||||||
机构编码: 机构编码
|
|
||||||
权限: 权限
|
|
||||||
权限id: 权限id
|
|
||||||
查询路径权限角色: 查询路径权限角色
|
|
||||||
欢迎登录: 欢迎登录
|
|
||||||
没有收到手机号: 没有收到手机号
|
|
||||||
注册失败: 注册失败
|
|
||||||
注册成功: 注册成功
|
|
||||||
注册日期: 注册日期
|
|
||||||
注册账号: 注册账号
|
|
||||||
父机构id: 父机构id
|
|
||||||
父权限id: 父权限id
|
|
||||||
用户: 用户
|
|
||||||
用户id: 用户id
|
|
||||||
用户名: 用户名
|
|
||||||
用户名已被占用: 用户名已被占用
|
|
||||||
用户应用: 用户应用
|
|
||||||
用户状态: 用户状态
|
|
||||||
用户管理: 用户管理
|
|
||||||
用户角色: 用户角色
|
|
||||||
用户部门表: 用户部门表
|
|
||||||
登录成功: 登录成功
|
|
||||||
短信码已生成: 短信码已生成
|
|
||||||
短信验证码: 短信验证码
|
|
||||||
短信验证码错误或已过期,请重新获取: 短信验证码错误或已过期,请重新获取
|
|
||||||
确认密码: 确认密码
|
|
||||||
签退: 签退
|
|
||||||
类型: 类型
|
|
||||||
系统错误: 系统错误
|
|
||||||
系统错误,请稍后重试: 系统错误,请稍后重试
|
|
||||||
组织结构代码: 组织结构代码
|
|
||||||
网站域名: 网站域名
|
|
||||||
联系人: 联系人
|
|
||||||
联系人电话: 联系人电话
|
|
||||||
营业执照: 营业执照
|
|
||||||
角色id: 角色id
|
|
||||||
角色名称: 角色名称
|
|
||||||
角色权限表: 角色权限表
|
|
||||||
该手机号已注册,请直接登录: 该手机号已注册,请直接登录
|
|
||||||
请先发送并输入短信验证码: 请先发送并输入短信验证码
|
|
||||||
请输入密码: 请输入密码
|
|
||||||
请输入手机号: 请输入手机号
|
|
||||||
请输入用户名: 请输入用户名
|
|
||||||
调用参数: 调用参数
|
|
||||||
调用日期: 调用日期
|
|
||||||
调用时间戳: 调用时间戳
|
|
||||||
账号: 账号
|
|
||||||
路径: 路径
|
|
||||||
身份证: 身份证
|
|
||||||
远程IP: 远程IP
|
|
||||||
连续失败次数: 连续失败次数
|
|
||||||
邮件地址: 邮件地址
|
|
||||||
邮箱: 邮箱
|
|
||||||
部门id: 部门id
|
|
||||||
重置密码: 重置密码
|
|
||||||
错误: 错误
|
|
||||||
需要短信验证key: 需要短信验证key
|
|
||||||
需输入手机号: 需输入手机号
|
|
||||||
需输入验证码: 需输入验证码
|
|
||||||
验证失败: 验证失败
|
|
||||||
验证码: 验证码
|
|
||||||
@ -11,15 +11,11 @@
|
|||||||
"alters":{}
|
"alters":{}
|
||||||
},
|
},
|
||||||
"edit_exclouded_fields":[],
|
"edit_exclouded_fields":[],
|
||||||
"parentField":"parentid"
|
"parentField":"parentid",
|
||||||
|
"toolbar":{
|
||||||
},
|
},
|
||||||
"subtables":[
|
"binds":[
|
||||||
{
|
|
||||||
"field":"permid",
|
|
||||||
"title":"权限角色",
|
|
||||||
"subtable":"rolepermission"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,34 +9,19 @@
|
|||||||
"exclouded": ["id", "password", "orgid", "nick_name" ],
|
"exclouded": ["id", "password", "orgid", "nick_name" ],
|
||||||
"cwidth": {}
|
"cwidth": {}
|
||||||
},
|
},
|
||||||
"editexclouded": ["id", "nick_name", "orgid", "last_login_fail", "last_login", "sync_from", "login_fail_count", "created_at"],
|
"editexclouded": [
|
||||||
"record_toolbar": [
|
"id", "nick_name", "orgid"
|
||||||
{
|
|
||||||
"label": "启用",
|
|
||||||
"actiontype": "dspy",
|
|
||||||
"url": "/rbac/users/enable_user.dspy",
|
|
||||||
"options": {
|
|
||||||
"icon": "check",
|
|
||||||
"cwidth": 16,
|
|
||||||
"cheight": 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "禁用",
|
|
||||||
"actiontype": "dspy",
|
|
||||||
"url": "/rbac/users/disable_user.dspy",
|
|
||||||
"options": {
|
|
||||||
"icon": "block",
|
|
||||||
"cwidth": 16,
|
|
||||||
"cheight": 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"subtables": [
|
"subtables": [
|
||||||
{
|
{
|
||||||
"field":"userid",
|
"field":"userid",
|
||||||
"title":"用户角色",
|
"title":"用户角色",
|
||||||
"subtable":"userrole"
|
"subtable":"userrole"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field":"userid",
|
||||||
|
"title":"APIKEY",
|
||||||
|
"subtable":"userapp"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@
|
|||||||
{
|
{
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"title": "注册日期",
|
"title": "注册日期",
|
||||||
"type": "date"
|
"type": "timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "last_login",
|
"name": "last_login",
|
||||||
|
|||||||
@ -119,13 +119,7 @@ async def register_user(sor, ns):
|
|||||||
ns.login_fail_count = 0
|
ns.login_fail_count = 0
|
||||||
ns1 = DictObject(id=id, orgname=ns.username)
|
ns1 = DictObject(id=id, orgname=ns.username)
|
||||||
await create_org(sor, ns1)
|
await create_org(sor, ns1)
|
||||||
roles = [
|
await create_user(sor, ns)
|
||||||
{
|
|
||||||
'orgtypeid': 'customer',
|
|
||||||
'roles': ['customer', 'admin']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
await create_user(sor, ns, roles)
|
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"data": {
|
"data": {
|
||||||
@ -158,11 +152,6 @@ async def checkUserPassword(request, username, password):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
user = recs[0]
|
user = recs[0]
|
||||||
# Check user status (disabled)
|
|
||||||
user_status = getattr(user, 'user_status', '0') or '0'
|
|
||||||
if user_status != '0':
|
|
||||||
debug(f'User {username} is disabled (status={user_status})')
|
|
||||||
return False
|
|
||||||
fail_count = getattr(user, 'login_fail_count', 0) or 0
|
fail_count = getattr(user, 'login_fail_count', 0) or 0
|
||||||
last_fail = getattr(user, 'last_login_fail', None)
|
last_fail = getattr(user, 'last_login_fail', None)
|
||||||
|
|
||||||
@ -214,11 +203,6 @@ async def basic_auth(sor, request):
|
|||||||
return None
|
return None
|
||||||
# Check lockout in Python layer (DB-agnostic)
|
# Check lockout in Python layer (DB-agnostic)
|
||||||
user = recs[0]
|
user = recs[0]
|
||||||
# Check user status (disabled)
|
|
||||||
user_status = getattr(user, 'user_status', '0') or '0'
|
|
||||||
if user_status != '0':
|
|
||||||
debug(f'User {username} is disabled (status={user_status}) via basic auth')
|
|
||||||
return None
|
|
||||||
fail_count = getattr(user, 'login_fail_count', 0) or 0
|
fail_count = getattr(user, 'login_fail_count', 0) or 0
|
||||||
last_fail = getattr(user, 'last_login_fail', None)
|
last_fail = getattr(user, 'last_login_fail', None)
|
||||||
if _is_locked(fail_count, last_fail):
|
if _is_locked(fail_count, last_fail):
|
||||||
|
|||||||
136
rbac/init.py
136
rbac/init.py
@ -1,9 +1,15 @@
|
|||||||
from ahserver.auth_api import AuthAPI
|
from ahserver.auth_api import AuthAPI
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
from .orgs import (
|
from .orgs import (
|
||||||
get_platform_providers
|
get_platform_providers
|
||||||
)
|
)
|
||||||
from .userperm import UserPermissions
|
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 (
|
from rbac.check_perm import (
|
||||||
objcheckperm,
|
objcheckperm,
|
||||||
get_org_users,
|
get_org_users,
|
||||||
@ -19,66 +25,8 @@ from rbac.set_role_perms import (
|
|||||||
set_role_perm,
|
set_role_perm,
|
||||||
set_role_perms
|
set_role_perms
|
||||||
)
|
)
|
||||||
from sqlor.dbpools import DBPools
|
from appPublic.log import debug
|
||||||
|
from ahserver.cache_sync import get_cache_sync
|
||||||
|
|
||||||
def _get_rbac_dbname():
|
|
||||||
env = ServerEnv()
|
|
||||||
return env.get_module_dbname('rbac')
|
|
||||||
|
|
||||||
|
|
||||||
async def on_rbac_role_event(data):
|
|
||||||
"""role 表变更后,全量失效 rp_caches"""
|
|
||||||
up = ServerEnv().userpermissions
|
|
||||||
up.invalidate_rp_cache()
|
|
||||||
|
|
||||||
|
|
||||||
async def on_rbac_userrole_event(data):
|
|
||||||
"""userrole 表变更后,精确失效对应用户的 ur_caches"""
|
|
||||||
ns = data.get('ns', {})
|
|
||||||
userid = ns.get('userid')
|
|
||||||
up = ServerEnv().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 = ServerEnv().userpermissions
|
|
||||||
up.invalidate_rp_cache()
|
|
||||||
|
|
||||||
|
|
||||||
async def on_rbac_rolepermission_event(data):
|
|
||||||
"""rolepermission 表变更后,全量失效 rp_caches"""
|
|
||||||
up = ServerEnv().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):
|
async def get_owner_orgid(*args, **kw):
|
||||||
return '0'
|
return '0'
|
||||||
@ -86,6 +34,58 @@ async def get_owner_orgid(*args, **kw):
|
|||||||
async def sor_get_owner_orgid(sor, orgid):
|
async def sor_get_owner_orgid(sor, orgid):
|
||||||
return '0'
|
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}')
|
||||||
|
|
||||||
|
|
||||||
|
async def start_cache_sync():
|
||||||
|
"""Start cache_sync and register RBAC reload callbacks."""
|
||||||
|
env = ServerEnv()
|
||||||
|
cache_sync = get_cache_sync()
|
||||||
|
|
||||||
|
# Get Redis URL from session config
|
||||||
|
try:
|
||||||
|
redis_url = env.conf.website.session_redis.url
|
||||||
|
except AttributeError:
|
||||||
|
redis_url = "redis://127.0.0.1:6379"
|
||||||
|
|
||||||
|
await cache_sync.start(redis_url)
|
||||||
|
debug(f'RBAC cache_sync started with Redis URL: {redis_url}')
|
||||||
|
|
||||||
|
# Register callbacks for cache invalidation messages from other processes
|
||||||
|
up = env.userpermissions
|
||||||
|
cache_sync.register('rbac:rp', up.invalidate_rp_cache)
|
||||||
|
cache_sync.register('rbac:ur:all', up.invalidate_all_user_caches)
|
||||||
|
# Note: rbac:ur:{userid} callbacks are handled by the invalidate_user_cache method itself
|
||||||
|
|
||||||
|
|
||||||
def load_rbac():
|
def load_rbac():
|
||||||
AuthAPI.checkUserPermission = objcheckperm
|
AuthAPI.checkUserPermission = objcheckperm
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
@ -103,11 +103,19 @@ def load_rbac():
|
|||||||
env.sor_get_org_users = sor_get_org_users
|
env.sor_get_org_users = sor_get_org_users
|
||||||
env.get_owner_orgid = get_owner_orgid
|
env.get_owner_orgid = get_owner_orgid
|
||||||
env.sor_add_user_roles = sor_add_user_roles
|
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
|
# Cache invalidation methods for use after role/permission changes
|
||||||
env.invalidate_user_perm_cache = env.userpermissions.invalidate_user_cache
|
env.invalidate_user_perm_cache = env.userpermissions.invalidate_user_cache
|
||||||
env.invalidate_all_perm_caches = env.userpermissions.invalidate_all_user_caches
|
env.invalidate_all_perm_caches = env.userpermissions.invalidate_all_user_caches
|
||||||
env.invalidate_role_perm_cache = env.userpermissions.invalidate_rp_cache
|
env.invalidate_role_perm_cache = env.userpermissions.invalidate_rp_cache
|
||||||
# Bind hot_reload event — instance method, WeakMethod safe (stored on env)
|
|
||||||
if hasattr(env, 'event_dispatcher'):
|
# Bind database events for automatic cache invalidation
|
||||||
env.event_dispatcher.bind('hot_reload', env.userpermissions.on_hot_reload)
|
dbpools = DBPools()
|
||||||
register_rbac_event_listeners()
|
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')
|
||||||
|
|||||||
@ -114,6 +114,31 @@ async def set_role_perms(dbname, module, orgtype, role, items):
|
|||||||
for tblname in items:
|
for tblname in items:
|
||||||
await set_role_perm(dbname, module, orgtype, role, tblname)
|
await set_role_perm(dbname, module, orgtype, role, tblname)
|
||||||
|
|
||||||
|
async def send_rbac_invalidation():
|
||||||
|
"""Send cache invalidation message to all processes via Redis Pub/Sub."""
|
||||||
|
try:
|
||||||
|
from ahserver.cache_sync import get_cache_sync
|
||||||
|
cache_sync = get_cache_sync()
|
||||||
|
# Use default Redis URL for CLI scripts
|
||||||
|
try:
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
env = ServerEnv()
|
||||||
|
redis_url = env.conf.website.session_redis.url
|
||||||
|
except (AttributeError, Exception):
|
||||||
|
redis_url = "redis://127.0.0.1:6379"
|
||||||
|
|
||||||
|
await cache_sync.start(redis_url)
|
||||||
|
# Invalidate both role-permission and all user caches
|
||||||
|
# (CLI scripts typically change permissions/roles)
|
||||||
|
await cache_sync.invalidate('rbac:rp')
|
||||||
|
await cache_sync.invalidate('rbac:ur:all')
|
||||||
|
debug('RBAC CLI: sent cache invalidation messages')
|
||||||
|
# Give a moment for the message to be published
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
await cache_sync.stop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Warning: Failed to send cache invalidation: {e}')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
async def main():
|
async def main():
|
||||||
if len(sys.argv) < 6:
|
if len(sys.argv) < 6:
|
||||||
@ -124,6 +149,8 @@ if __name__ == '__main__':
|
|||||||
orgtype = sys.argv[3]
|
orgtype = sys.argv[3]
|
||||||
role = sys.argv[4]
|
role = sys.argv[4]
|
||||||
await set_role_perms(dbname, module, orgtype, role, sys.argv[5:])
|
await set_role_perms(dbname, module, orgtype, role, sys.argv[5:])
|
||||||
|
# Send invalidation message to all running Sage processes
|
||||||
|
await send_rbac_invalidation()
|
||||||
|
|
||||||
def run(coro):
|
def run(coro):
|
||||||
p = '.'
|
p = '.'
|
||||||
|
|||||||
140
rbac/userperm.py
140
rbac/userperm.py
@ -4,20 +4,9 @@ from sqlor.dbpools import get_sor_context
|
|||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from appPublic.Singleton import SingletonDecorator
|
from appPublic.Singleton import SingletonDecorator
|
||||||
from appPublic.log import debug, error
|
from appPublic.log import debug, error
|
||||||
from appPublic.jsonConfig import getConfig
|
from ahserver.cache_sync import get_cache_sync
|
||||||
|
|
||||||
|
|
||||||
def _cache_enabled(module_name='rbac'):
|
|
||||||
"""Check if cache is enabled for the given module in config.json"""
|
|
||||||
try:
|
|
||||||
config = getConfig()
|
|
||||||
module_cache = config.module_cache
|
|
||||||
if module_cache is None:
|
|
||||||
return True # Default to enabled if not configured
|
|
||||||
return getattr(module_cache, module_name, True)
|
|
||||||
except Exception:
|
|
||||||
return True # Default to enabled on error
|
|
||||||
|
|
||||||
class LRUCache:
|
class LRUCache:
|
||||||
"""Async-safe LRU cache with TTL support.
|
"""Async-safe LRU cache with TTL support.
|
||||||
|
|
||||||
@ -38,8 +27,6 @@ class LRUCache:
|
|||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
import time
|
import time
|
||||||
if not _cache_enabled('rbac'):
|
|
||||||
return None
|
|
||||||
if key not in self._cache:
|
if key not in self._cache:
|
||||||
return None
|
return None
|
||||||
value, expire_at = self._cache[key]
|
value, expire_at = self._cache[key]
|
||||||
@ -51,8 +38,6 @@ class LRUCache:
|
|||||||
|
|
||||||
def set(self, key, value):
|
def set(self, key, value):
|
||||||
import time
|
import time
|
||||||
if not _cache_enabled('rbac'):
|
|
||||||
return
|
|
||||||
if key in self._cache:
|
if key in self._cache:
|
||||||
self._cache.move_to_end(key)
|
self._cache.move_to_end(key)
|
||||||
self._cache[key] = (value, time.time() + self.ttl)
|
self._cache[key] = (value, time.time() + self.ttl)
|
||||||
@ -98,89 +83,82 @@ class UserPermissions:
|
|||||||
# Async lock for rp_caches initialization (lazy init)
|
# Async lock for rp_caches initialization (lazy init)
|
||||||
self._rp_lock = None
|
self._rp_lock = None
|
||||||
|
|
||||||
def on_hot_reload(self, data=None):
|
async def on_user_update(self, data):
|
||||||
"""Event handler for hot_reload event. Clears all caches."""
|
|
||||||
from appPublic.log import debug
|
|
||||||
debug(f'[rbac] on_hot_reload called, clearing caches (data={data})')
|
|
||||||
self.ur_caches.clear()
|
|
||||||
self.invalidate_rp_cache()
|
|
||||||
|
|
||||||
def on_user_update(self, data):
|
|
||||||
"""Event handler for users table update.
|
"""Event handler for users table update.
|
||||||
Clears the specific user's permission cache.
|
Clears the specific user's permission cache.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
userid = getattr(data, 'id', None)
|
userid = getattr(data, 'id', None)
|
||||||
if userid:
|
if userid:
|
||||||
self.invalidate_user_cache(userid)
|
await self.invalidate_user_cache(userid)
|
||||||
debug(f'RBAC cache invalidated for user id={userid} (users update)')
|
debug(f'RBAC cache invalidated for user id={userid} (users update)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_user_update handler error: {e}')
|
error(f'RBAC on_user_update handler error: {e}')
|
||||||
|
|
||||||
def on_user_create(self, data):
|
async def on_user_create(self, data):
|
||||||
"""Event handler for users table insert.
|
"""Event handler for users table insert.
|
||||||
Clears the specific user's permission cache.
|
Clears the specific user's permission cache.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
userid = getattr(data, 'id', None)
|
userid = getattr(data, 'id', None)
|
||||||
if userid:
|
if userid:
|
||||||
self.invalidate_user_cache(userid)
|
await self.invalidate_user_cache(userid)
|
||||||
debug(f'RBAC cache invalidated for user id={userid} (users create)')
|
debug(f'RBAC cache invalidated for user id={userid} (users create)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_user_create handler error: {e}')
|
error(f'RBAC on_user_create handler error: {e}')
|
||||||
|
|
||||||
def on_user_delete(self, data):
|
async def on_user_delete(self, data):
|
||||||
"""Event handler for users table delete.
|
"""Event handler for users table delete.
|
||||||
Clears the specific user's permission cache.
|
Clears the specific user's permission cache.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
userid = getattr(data, 'id', None)
|
userid = getattr(data, 'id', None)
|
||||||
if userid:
|
if userid:
|
||||||
self.invalidate_user_cache(userid)
|
await self.invalidate_user_cache(userid)
|
||||||
debug(f'RBAC cache invalidated for user id={userid} (users delete)')
|
debug(f'RBAC cache invalidated for user id={userid} (users delete)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_user_delete handler error: {e}')
|
error(f'RBAC on_user_delete handler error: {e}')
|
||||||
|
|
||||||
def on_rolepermission_change(self, data):
|
async def on_rolepermission_change(self, data):
|
||||||
"""Event handler for rolepermission table C/U/D.
|
"""Event handler for rolepermission table C/U/D.
|
||||||
Clears the role-permission cache.
|
Clears the role-permission cache.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.invalidate_rp_cache()
|
await self.invalidate_rp_cache()
|
||||||
debug('RBAC role-permission cache invalidated (rolepermission change)')
|
debug('RBAC role-permission cache invalidated (rolepermission change)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_rolepermission_change handler error: {e}')
|
error(f'RBAC on_rolepermission_change handler error: {e}')
|
||||||
|
|
||||||
def on_permission_change(self, data):
|
async def on_permission_change(self, data):
|
||||||
"""Event handler for permission table update.
|
"""Event handler for permission table update.
|
||||||
Clears the role-permission cache.
|
Clears the role-permission cache.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.invalidate_rp_cache()
|
await self.invalidate_rp_cache()
|
||||||
debug('RBAC role-permission cache invalidated (permission change)')
|
debug('RBAC role-permission cache invalidated (permission change)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_permission_change handler error: {e}')
|
error(f'RBAC on_permission_change handler error: {e}')
|
||||||
|
|
||||||
def on_role_change(self, data):
|
async def on_role_change(self, data):
|
||||||
"""Event handler for role table C/U/D.
|
"""Event handler for role table C/U/D.
|
||||||
Clears all user caches and role-permission cache,
|
Clears all user caches and role-permission cache,
|
||||||
since role changes may affect any user.
|
since role changes may affect any user.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.invalidate_all_user_caches()
|
await self.invalidate_all_user_caches()
|
||||||
self.invalidate_rp_cache()
|
await self.invalidate_rp_cache()
|
||||||
debug('RBAC all caches invalidated (role change)')
|
debug('RBAC all caches invalidated (role change)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_role_change handler error: {e}')
|
error(f'RBAC on_role_change handler error: {e}')
|
||||||
|
|
||||||
def on_userrole_change(self, data):
|
async def on_userrole_change(self, data):
|
||||||
"""Event handler for userrole table C/U/D.
|
"""Event handler for userrole table C/U/D.
|
||||||
Clears the specific user's permission cache based on userid.
|
Clears the specific user's permission cache based on userid.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
userid = getattr(data, 'userid', None)
|
userid = getattr(data, 'userid', None)
|
||||||
if userid:
|
if userid:
|
||||||
self.invalidate_user_cache(userid)
|
await self.invalidate_user_cache(userid)
|
||||||
debug(f'RBAC cache invalidated for user id={userid} (userrole change)')
|
debug(f'RBAC cache invalidated for user id={userid} (userrole change)')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error(f'RBAC on_userrole_change handler error: {e}')
|
error(f'RBAC on_userrole_change handler error: {e}')
|
||||||
@ -200,28 +178,41 @@ class UserPermissions:
|
|||||||
return roles
|
return roles
|
||||||
|
|
||||||
async with get_sor_context(ServerEnv(), 'rbac') as sor:
|
async with get_sor_context(ServerEnv(), 'rbac') as sor:
|
||||||
roles = await self.get_userroles(sor, userid)
|
await self.get_userroles(sor, userid)
|
||||||
# When cache is enabled, get_userroles stored it and we can read back;
|
|
||||||
# when cache is disabled, get_userroles returns the list directly.
|
|
||||||
if roles is not None:
|
|
||||||
return roles
|
|
||||||
return self.ur_caches.get(userid)
|
return self.ur_caches.get(userid)
|
||||||
return ['any', 'logined']
|
return None
|
||||||
|
|
||||||
def invalidate_user_cache(self, userid):
|
async def invalidate_user_cache(self, userid):
|
||||||
"""Invalidate cache for a specific user.
|
"""Invalidate cache for a specific user.
|
||||||
Call this after role changes, user creation, etc.
|
Call this after role changes, user creation, etc.
|
||||||
|
Also broadcasts invalidation to all other processes via Redis Pub/Sub.
|
||||||
"""
|
"""
|
||||||
self.ur_caches.invalidate(userid)
|
self.ur_caches.invalidate(userid)
|
||||||
|
# Broadcast to other processes
|
||||||
|
cache_sync = get_cache_sync()
|
||||||
|
if cache_sync.is_running:
|
||||||
|
await cache_sync.invalidate(f'rbac:ur:{userid}')
|
||||||
|
|
||||||
def invalidate_all_user_caches(self):
|
async def invalidate_all_user_caches(self):
|
||||||
"""Invalidate all user role caches."""
|
"""Invalidate all user role caches.
|
||||||
|
Also broadcasts invalidation to all other processes via Redis Pub/Sub.
|
||||||
|
"""
|
||||||
self.ur_caches.clear()
|
self.ur_caches.clear()
|
||||||
|
# Broadcast to other processes
|
||||||
|
cache_sync = get_cache_sync()
|
||||||
|
if cache_sync.is_running:
|
||||||
|
await cache_sync.invalidate('rbac:ur:all')
|
||||||
|
|
||||||
def invalidate_rp_cache(self):
|
async def invalidate_rp_cache(self):
|
||||||
"""Invalidate role-permission cache (after permission changes)."""
|
"""Invalidate role-permission cache (after permission changes).
|
||||||
|
Also broadcasts invalidation to all other processes via Redis Pub/Sub.
|
||||||
|
"""
|
||||||
self.rp_caches = None
|
self.rp_caches = None
|
||||||
self.rp_cache_loaded_at = 0
|
self.rp_cache_loaded_at = 0
|
||||||
|
# Broadcast to other processes
|
||||||
|
cache_sync = get_cache_sync()
|
||||||
|
if cache_sync.is_running:
|
||||||
|
await cache_sync.invalidate('rbac:rp')
|
||||||
|
|
||||||
async def load_roleperms(self, sor):
|
async def load_roleperms(self, sor):
|
||||||
"""Load all role-permission mappings into cache.
|
"""Load all role-permission mappings into cache.
|
||||||
@ -235,28 +226,22 @@ class UserPermissions:
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
# Fast path: cache valid, no lock needed
|
# Fast path: cache valid, no lock needed
|
||||||
if _cache_enabled('rbac') and self.rp_caches is not None and (now - self.rp_cache_loaded_at) < self.rp_cache_ttl:
|
if self.rp_caches is not None and (now - self.rp_cache_loaded_at) < self.rp_cache_ttl:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Slow path: acquire lock and double-check
|
# Slow path: acquire lock and double-check
|
||||||
async with self._get_rp_lock():
|
async with self._get_rp_lock():
|
||||||
# Double-check after lock acquisition
|
# Double-check after lock acquisition
|
||||||
if _cache_enabled('rbac') and self.rp_caches is not None and (now - self.rp_cache_loaded_at) < self.rp_cache_ttl:
|
if self.rp_caches is not None and (now - self.rp_cache_loaded_at) < self.rp_cache_ttl:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Build in local dict first, assign atomically when complete.
|
self.rp_caches = {}
|
||||||
# Otherwise other coroutines see {} during the await and get 403.
|
|
||||||
new_caches = {}
|
|
||||||
sql_all = """select c.id, c.orgtypeid, c.name, b.path
|
sql_all = """select c.id, c.orgtypeid, c.name, b.path
|
||||||
from rolepermission a, permission b, role c
|
from rolepermission a, permission b, role c
|
||||||
where a.permid = b.id
|
where a.permid = b.id
|
||||||
and c.id = a.roleid
|
and c.id = a.roleid
|
||||||
order by c.orgtypeid, c.name"""
|
order by c.orgtypeid, c.name"""
|
||||||
recs = await sor.sqlExe(sql_all, {})
|
recs = await sor.sqlExe(sql_all, {})
|
||||||
if len(recs) == 0 and self.rp_caches:
|
|
||||||
# DB returned empty — likely a bad connection. Keep previous valid cache.
|
|
||||||
debug(f'load_roleperms: got 0 records, keeping previous cache ({sum(len(v) for v in self.rp_caches.values())} paths)')
|
|
||||||
return
|
|
||||||
for r in recs:
|
for r in recs:
|
||||||
if r.id == 'anonymous':
|
if r.id == 'anonymous':
|
||||||
k = 'anonymous'
|
k = 'anonymous'
|
||||||
@ -266,17 +251,13 @@ order by c.orgtypeid, c.name"""
|
|||||||
k = 'logined'
|
k = 'logined'
|
||||||
else:
|
else:
|
||||||
k = f'{r.orgtypeid}.{r.name}'
|
k = f'{r.orgtypeid}.{r.name}'
|
||||||
arr = new_caches.get(k, [])
|
arr = self.rp_caches.get(k, [])
|
||||||
arr.append(r.path)
|
arr.append(r.path)
|
||||||
new_caches[k] = arr
|
self.rp_caches[k] = arr
|
||||||
# Atomic swap: other coroutines see old cache or fully-loaded new cache, never {}
|
|
||||||
self.rp_caches = new_caches
|
|
||||||
self.rp_cache_loaded_at = now
|
self.rp_cache_loaded_at = now
|
||||||
|
|
||||||
async def get_userroles(self, sor, userid):
|
async def get_userroles(self, sor, userid):
|
||||||
"""Load user roles from database and cache them.
|
"""Load user roles from database and cache them."""
|
||||||
Returns the roles list directly (needed when cache is disabled).
|
|
||||||
"""
|
|
||||||
recs = await sor.sqlExe('''select b.id, b.orgtypeid, b.name
|
recs = await sor.sqlExe('''select b.id, b.orgtypeid, b.name
|
||||||
from users a, role b, userrole c
|
from users a, role b, userrole c
|
||||||
where a.id = c.userid
|
where a.id = c.userid
|
||||||
@ -287,17 +268,14 @@ where a.id = c.userid
|
|||||||
roles.append(f'{r.orgtypeid}.{r.name}')
|
roles.append(f'{r.orgtypeid}.{r.name}')
|
||||||
roles.append(f'{r.orgtypeid}.*')
|
roles.append(f'{r.orgtypeid}.*')
|
||||||
roles.append(f'*.{r.name}')
|
roles.append(f'*.{r.name}')
|
||||||
roles = sorted(list(set(roles)))
|
self.ur_caches.set(userid, sorted(list(set(roles))))
|
||||||
self.ur_caches.set(userid, roles)
|
|
||||||
return roles
|
|
||||||
|
|
||||||
def check_roles_path(self, roles, path):
|
def check_roles_path(self, roles, path):
|
||||||
"""Check if any of the roles has access to the given path.
|
"""Check if any of the roles has access to the given path.
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
- Exact match: '/customer_management/index.ui' or '/main/login.ui'
|
- Exact match: '/customer_management/index.ui' or '/main/login.ui'
|
||||||
- Wildcard prefix match: '/customer_management/**' or '/customer_management/%'
|
- Wildcard prefix match: '/customer_management/**' matches any path starting with '/customer_management/'
|
||||||
matches any path starting with '/customer_management/'
|
|
||||||
- Path normalization: tries both the raw path and path with /main stripped
|
- Path normalization: tries both the raw path and path with /main stripped
|
||||||
"""
|
"""
|
||||||
for role in roles:
|
for role in roles:
|
||||||
@ -314,21 +292,15 @@ where a.id = c.userid
|
|||||||
return True
|
return True
|
||||||
# Also try wildcard match with normalized path
|
# Also try wildcard match with normalized path
|
||||||
for perm_path in paths:
|
for perm_path in paths:
|
||||||
prefix = None
|
|
||||||
if perm_path.endswith('**'):
|
if perm_path.endswith('**'):
|
||||||
prefix = perm_path[:-2]
|
prefix = perm_path[:-2]
|
||||||
elif perm_path.endswith('%'):
|
if normalized.startswith(prefix) or path.startswith(prefix):
|
||||||
prefix = perm_path[:-1]
|
|
||||||
if prefix and (normalized.startswith(prefix) or path.startswith(prefix)):
|
|
||||||
return True
|
return True
|
||||||
# Wildcard prefix match with raw path
|
# Wildcard prefix match with raw path
|
||||||
for perm_path in paths:
|
for perm_path in paths:
|
||||||
prefix = None
|
|
||||||
if perm_path.endswith('**'):
|
if perm_path.endswith('**'):
|
||||||
prefix = perm_path[:-2]
|
prefix = perm_path[:-2]
|
||||||
elif perm_path.endswith('%'):
|
if path.startswith(prefix):
|
||||||
prefix = perm_path[:-1]
|
|
||||||
if prefix and path.startswith(prefix):
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -344,19 +316,13 @@ where a.id = c.userid
|
|||||||
if userid is None:
|
if userid is None:
|
||||||
roles = ['any', 'anonymous']
|
roles = ['any', 'anonymous']
|
||||||
|
|
||||||
if not _cache_enabled('rbac') or self.rp_caches is None or not roles:
|
if self.rp_caches is None or not roles:
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
async with get_sor_context(env, 'rbac') as sor:
|
async with get_sor_context(env, 'rbac') as sor:
|
||||||
if not _cache_enabled('rbac') or self.rp_caches is None:
|
if self.rp_caches is None:
|
||||||
await self.load_roleperms(sor)
|
await self.load_roleperms(sor)
|
||||||
if not roles:
|
if not roles:
|
||||||
roles = await self.get_userroles(sor, userid)
|
await self.get_userroles(sor, userid)
|
||||||
# When cache is enabled, fall back to cache read
|
|
||||||
if roles is None:
|
|
||||||
roles = self.ur_caches.get(userid)
|
roles = self.ur_caches.get(userid)
|
||||||
|
|
||||||
# Safety fallback: if roles is still None (shouldn't happen), deny access
|
|
||||||
if not roles:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self.check_roles_path(roles, path)
|
return self.check_roles_path(roles, path)
|
||||||
|
|||||||
@ -1,96 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""rbac 模块 RBAC 权限注册。"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
MOD = "rbac"
|
|
||||||
|
|
||||||
# any — 无需登录(登录页、注册、验证码、公共资源等)
|
|
||||||
PATHS_ANY = [
|
|
||||||
f"/{MOD}/admin_menu.ui",
|
|
||||||
f"/{MOD}/gen_sms_code.dspy",
|
|
||||||
f"/{MOD}/login.css",
|
|
||||||
f"/{MOD}/phone_login.dspy",
|
|
||||||
f"/{MOD}/qr_scan.ui",
|
|
||||||
f"/{MOD}/user/code_login.dspy",
|
|
||||||
f"/{MOD}/user/login.ui",
|
|
||||||
f"/{MOD}/user/logout.dspy",
|
|
||||||
f"/{MOD}/user/register.dspy",
|
|
||||||
f"/{MOD}/user/register.ui",
|
|
||||||
f"/{MOD}/user/sms_register.dspy",
|
|
||||||
f"/{MOD}/user/reset_password/index.ui",
|
|
||||||
f"/{MOD}/user/reset_password/reset_password.dspy",
|
|
||||||
f"/{MOD}/user/up_login.dspy",
|
|
||||||
f"/{MOD}/usermenu.ui",
|
|
||||||
f"/{MOD}/userpassword_login.dspy",
|
|
||||||
f"/{MOD}/userpassword_login.ui",
|
|
||||||
# 公共资源
|
|
||||||
"/favicon.ico",
|
|
||||||
"/i18n_getmsgs",
|
|
||||||
]
|
|
||||||
|
|
||||||
# logined — 需要认证的页面和 API
|
|
||||||
PATHS_LOGINED = [
|
|
||||||
f"/{MOD}",
|
|
||||||
f"/{MOD}/add_adminuser.dspy",
|
|
||||||
f"/{MOD}/add_adminuser.ui",
|
|
||||||
f"/{MOD}/add_provider.dspy",
|
|
||||||
f"/{MOD}/add_provider.ui",
|
|
||||||
f"/{MOD}/add_reseller.dspy",
|
|
||||||
f"/{MOD}/add_superuser.dspy",
|
|
||||||
f"/{MOD}/find_unauth_files.dspy",
|
|
||||||
f"/{MOD}/get_all_roles.dspy",
|
|
||||||
f"/{MOD}/get_normal_roles.dspy",
|
|
||||||
f"/{MOD}/get_provider.dspy",
|
|
||||||
f"/{MOD}/get_reseller.dspy",
|
|
||||||
f"/{MOD}/index.ui",
|
|
||||||
f"/{MOD}/list_path_roles.dspy",
|
|
||||||
f"/{MOD}/list_path_roles.ui",
|
|
||||||
f"/{MOD}/organization",
|
|
||||||
f"/{MOD}/orgtypes",
|
|
||||||
f"/{MOD}/permission",
|
|
||||||
f"/{MOD}/provider",
|
|
||||||
f"/{MOD}/refresh_userperm.dspy",
|
|
||||||
f"/{MOD}/reseller",
|
|
||||||
f"/{MOD}/role",
|
|
||||||
f"/{MOD}/rolepermission",
|
|
||||||
f"/{MOD}/stat_active_users.ui",
|
|
||||||
f"/{MOD}/stat_total_orgs.ui",
|
|
||||||
f"/{MOD}/stat_total_users.ui",
|
|
||||||
f"/{MOD}/user",
|
|
||||||
f"/{MOD}/user/myrole.ui",
|
|
||||||
f"/{MOD}/user/user.ui",
|
|
||||||
f"/{MOD}/user/user_panel.ui",
|
|
||||||
f"/{MOD}/user/userapikey",
|
|
||||||
f"/{MOD}/user/userapikey/add_userapikey.dspy",
|
|
||||||
f"/{MOD}/user/userapikey/delete_userapikey.dspy",
|
|
||||||
f"/{MOD}/user/userapikey/get_userapikey.dspy",
|
|
||||||
f"/{MOD}/user/userapikey/index.ui",
|
|
||||||
f"/{MOD}/user/userapikey/update_userapikey.dspy",
|
|
||||||
f"/{MOD}/user/userinfo.ui",
|
|
||||||
f"/{MOD}/user/edit_profile.dspy",
|
|
||||||
f"/{MOD}/user/save_profile.dspy",
|
|
||||||
f"/{MOD}/user/wechat_login.ui",
|
|
||||||
f"/{MOD}/userapp",
|
|
||||||
f"/{MOD}/userdepartment",
|
|
||||||
f"/{MOD}/userrole",
|
|
||||||
f"/{MOD}/users",
|
|
||||||
f"/{MOD}/usersync",
|
|
||||||
f"/{MOD}/usersync/index.dspy",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_paths():
|
|
||||||
for path in PATHS_ANY:
|
|
||||||
subprocess.run(["py3/bin/python", "set_role_perm.py", "any", path])
|
|
||||||
print(f" any: {path}")
|
|
||||||
|
|
||||||
for path in PATHS_LOGINED:
|
|
||||||
subprocess.run(["py3/bin/python", "set_role_perm.py", "logined", path])
|
|
||||||
print(f" logined: {path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(f"=== {MOD} RBAC registration ===")
|
|
||||||
register_paths()
|
|
||||||
print("Done.")
|
|
||||||
@ -7,22 +7,12 @@ if phone is None:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
# 使用短信模块发布的sms_engine实例生成验证码,参数手机号
|
# 使用短信模块发布的sms_engine实例生成验证码,参数手机号
|
||||||
try:
|
|
||||||
xx = await sms_engine.generate_sms_code(phone)
|
xx = await sms_engine.generate_sms_code(phone)
|
||||||
except Exception as e:
|
|
||||||
debug(f'gen_sms_code error: {e}')
|
|
||||||
exception(f'gen_sms_code error for {phone}: {e}')
|
|
||||||
return {
|
|
||||||
"status": "error",
|
|
||||||
"data": {
|
|
||||||
"message": f"发送验证码出错: {e}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if xx is None:
|
if xx is None:
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"data": {
|
"data": {
|
||||||
"message": "发送验证码出错,请检查短信模板配置和百度API连接"
|
"message": "发送验证码出错"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id, code = xx
|
id, code = xx
|
||||||
|
|||||||
212
wwwroot/index.ui
Normal file
212
wwwroot/index.ui
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
{% 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,242 +0,0 @@
|
|||||||
/* ===== Modern Login Popup Styling ===== */
|
|
||||||
|
|
||||||
/* Popup window card */
|
|
||||||
.login-window {
|
|
||||||
border-radius: 16px !important;
|
|
||||||
box-shadow: 0 25px 60px -12px rgba(0, 0, 0, 0.35) !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title bar - gradient brand header */
|
|
||||||
.login-window .titlebar {
|
|
||||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%) !important;
|
|
||||||
padding: 8px 16px !important;
|
|
||||||
border: none !important;
|
|
||||||
min-height: 48px !important;
|
|
||||||
}
|
|
||||||
.login-window .titlebar .text-w,
|
|
||||||
.login-window .titlebar .bricks-text {
|
|
||||||
color: #ffffff !important;
|
|
||||||
font-size: 16px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content area padding */
|
|
||||||
.login-window .flexbox {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab panel modern styling */
|
|
||||||
.login-window .tabpanel {
|
|
||||||
background: transparent !important;
|
|
||||||
border: none !important;
|
|
||||||
display: flex !important;
|
|
||||||
flex-direction: column !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-tabs {
|
|
||||||
display: flex !important;
|
|
||||||
border-bottom: 2px solid #e2e8f0 !important;
|
|
||||||
background: #f8fafc !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
gap: 0 !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-tab {
|
|
||||||
padding: 14px 24px !important;
|
|
||||||
cursor: pointer !important;
|
|
||||||
border-bottom: 3px solid transparent !important;
|
|
||||||
margin-bottom: -2px !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
color: #64748b !important;
|
|
||||||
font-weight: 500 !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-tab:hover {
|
|
||||||
color: #6366f1 !important;
|
|
||||||
background: rgba(99, 102, 241, 0.04) !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-tab-active,
|
|
||||||
.login-window .tabpanel-tab-selected {
|
|
||||||
color: #6366f1 !important;
|
|
||||||
border-bottom-color: #6366f1 !important;
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-content {
|
|
||||||
padding: 0 !important;
|
|
||||||
background: #ffffff !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
flex: 1 !important;
|
|
||||||
min-height: 0 !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-content .scrollpanel {
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
.login-window .tabpanel-content .scrollpanel .vcontainer {
|
|
||||||
padding: 20px 24px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form styling */
|
|
||||||
.login-window .vcontainer {
|
|
||||||
gap: 4px !important;
|
|
||||||
}
|
|
||||||
.login-window .inputbox {
|
|
||||||
border-radius: 10px !important;
|
|
||||||
border: 1.5px solid #e2e8f0 !important;
|
|
||||||
padding: 10px 14px !important;
|
|
||||||
transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
|
|
||||||
background: #f8fafc !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
.login-window .inputbox:focus,
|
|
||||||
.login-window .inputbox:focus-within {
|
|
||||||
border-color: #6366f1 !important;
|
|
||||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12) !important;
|
|
||||||
background: #ffffff !important;
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
.login-window input.inputbox {
|
|
||||||
height: 42px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form labels */
|
|
||||||
.login-window .field-label,
|
|
||||||
.login-window .bricks-form label {
|
|
||||||
font-weight: 500 !important;
|
|
||||||
color: #374151 !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
margin-bottom: 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar buttons */
|
|
||||||
.login-window .htoolbar {
|
|
||||||
padding: 8px 0 !important;
|
|
||||||
gap: 8px !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
}
|
|
||||||
.login-window .submit_btn {
|
|
||||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
|
|
||||||
color: #ffffff !important;
|
|
||||||
border: none !important;
|
|
||||||
border-radius: 10px !important;
|
|
||||||
padding: 10px 32px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
cursor: pointer !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3) !important;
|
|
||||||
}
|
|
||||||
.login-window .submit_btn:hover {
|
|
||||||
opacity: 0.92 !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4) !important;
|
|
||||||
transform: translateY(-1px) !important;
|
|
||||||
}
|
|
||||||
.login-window .reset_btn,
|
|
||||||
.login-window .clear_btn {
|
|
||||||
border-radius: 10px !important;
|
|
||||||
border: 1.5px solid #e2e8f0 !important;
|
|
||||||
background: #ffffff !important;
|
|
||||||
color: #64748b !important;
|
|
||||||
padding: 10px 20px !important;
|
|
||||||
font-weight: 500 !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
}
|
|
||||||
.login-window .reset_btn:hover,
|
|
||||||
.login-window .clear_btn:hover {
|
|
||||||
border-color: #cbd5e1 !important;
|
|
||||||
background: #f8fafc !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SMS send button */
|
|
||||||
.sms-send-btn {
|
|
||||||
border-radius: 10px !important;
|
|
||||||
border: 1.5px solid #6366f1 !important;
|
|
||||||
background: rgba(99, 102, 241, 0.06) !important;
|
|
||||||
color: #6366f1 !important;
|
|
||||||
padding: 8px 20px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
cursor: pointer !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
}
|
|
||||||
.sms-send-btn:hover {
|
|
||||||
background: rgba(99, 102, 241, 0.12) !important;
|
|
||||||
}
|
|
||||||
.sms-send-btn:disabled {
|
|
||||||
opacity: 0.5 !important;
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Description text */
|
|
||||||
.login-window .bricks-text {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form description */
|
|
||||||
.login-desc {
|
|
||||||
color: #94a3b8 !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
text-align: center !important;
|
|
||||||
margin-top: 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section separator */
|
|
||||||
.login-separator {
|
|
||||||
display: flex !important;
|
|
||||||
align-items: center !important;
|
|
||||||
gap: 12px !important;
|
|
||||||
margin: 8px 0 !important;
|
|
||||||
color: #cbd5e1 !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
.login-separator::before,
|
|
||||||
.login-separator::after {
|
|
||||||
content: '' !important;
|
|
||||||
flex: 1 !important;
|
|
||||||
height: 1px !important;
|
|
||||||
background: #e2e8f0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme support */
|
|
||||||
[data-theme="dark"] .login-window {
|
|
||||||
background: #1e293b !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .tabpanel-content {
|
|
||||||
background: #1e293b !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .tabpanel-tabs {
|
|
||||||
background: #0f172a !important;
|
|
||||||
border-bottom-color: #334155 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .tabpanel-tab {
|
|
||||||
color: #94a3b8 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .tabpanel-tab-active,
|
|
||||||
[data-theme="dark"] .login-window .tabpanel-tab-selected {
|
|
||||||
color: #818cf8 !important;
|
|
||||||
border-bottom-color: #818cf8 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .inputbox {
|
|
||||||
background: #0F172A !important;
|
|
||||||
border-color: #475569 !important;
|
|
||||||
color: #e2e8f0 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .inputbox:focus,
|
|
||||||
[data-theme="dark"] .login-window .inputbox:focus-within {
|
|
||||||
border-color: #818cf8 !important;
|
|
||||||
box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.15) !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .field-label {
|
|
||||||
color: #cbd5e1 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .login-window .reset_btn,
|
|
||||||
[data-theme="dark"] .login-window .clear_btn {
|
|
||||||
background: #334155 !important;
|
|
||||||
border-color: #475569 !important;
|
|
||||||
color: #cbd5e1 !important;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .sms-send-btn {
|
|
||||||
border-color: #818cf8 !important;
|
|
||||||
color: #818cf8 !important;
|
|
||||||
background: rgba(129, 140, 248, 0.1) !important;
|
|
||||||
}
|
|
||||||
@ -1,257 +0,0 @@
|
|||||||
# 手机验证码登录 - 接收前端表单参数(cell_no, codeid, check_code)
|
|
||||||
# 调用sms_engine验证后完成登录或自动注册
|
|
||||||
debug(f'code_login.dspy: {params_kw=}')
|
|
||||||
|
|
||||||
cellphone = params_kw.cell_no
|
|
||||||
key = params_kw.codeid
|
|
||||||
sms_code = params_kw.check_code
|
|
||||||
|
|
||||||
if not cellphone:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "错误",
|
|
||||||
"message": "需输入手机号"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not sms_code:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "错误",
|
|
||||||
"message": "需输入验证码"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not key:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "错误",
|
|
||||||
"message": "需要短信验证key"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证短信码
|
|
||||||
ok = await sms_engine.check_sms_code(key, sms_code)
|
|
||||||
if not ok:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "验证失败",
|
|
||||||
"message": "手机短信验证码出错"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证通过,查找或注册用户
|
|
||||||
ns = {
|
|
||||||
"username": cellphone,
|
|
||||||
"password": "^&%UHI",
|
|
||||||
"cfm_password": "^&%UHI",
|
|
||||||
"mobile": cellphone,
|
|
||||||
"user_status": "0"
|
|
||||||
}
|
|
||||||
udata = DictObject(**ns)
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with get_sor_context(request._run_ns, 'rbac') as sor:
|
|
||||||
recs = await sor.R('users', {'mobile': cellphone})
|
|
||||||
if recs:
|
|
||||||
if len(recs) == 1:
|
|
||||||
r = recs[0]
|
|
||||||
now_str = timestampstr()
|
|
||||||
await sor.sqlExe("""
|
|
||||||
UPDATE users
|
|
||||||
SET last_login = ${now}$, login_fail_count = 0,
|
|
||||||
last_login_fail = NULL
|
|
||||||
WHERE id = ${id}$
|
|
||||||
""", {'id': r.id, 'now': now_str})
|
|
||||||
await remember_user(r.id, username=r.username, userorgid=r.orgid)
|
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "登录成功",
|
|
||||||
"message": f"{r.username} 欢迎回来"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "window.user_container",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/userinfo.ui')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "body.login_window",
|
|
||||||
"script": "this.destroy()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
# 多个用户绑定同一手机号
|
|
||||||
if params_kw.selected_id:
|
|
||||||
for r in recs:
|
|
||||||
if r.id == params_kw.selected_id:
|
|
||||||
now_str = timestampstr()
|
|
||||||
await sor.sqlExe("""
|
|
||||||
UPDATE users
|
|
||||||
SET last_login = ${now}$, login_fail_count = 0,
|
|
||||||
last_login_fail = NULL
|
|
||||||
WHERE id = ${id}$
|
|
||||||
""", {'id': r.id, 'now': now_str})
|
|
||||||
await remember_user(r.id, username=r.username, userorgid=r.orgid)
|
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "登录成功",
|
|
||||||
"message": f"{r.username} 欢迎回来"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "window.user_container",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/userinfo.ui')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "body.login_window",
|
|
||||||
"script": "this.destroy()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# 返回用户选择列表
|
|
||||||
buttons = []
|
|
||||||
for r in recs:
|
|
||||||
buttons.append({
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"label": f"{r.username} ({r.id})",
|
|
||||||
"width": "100%",
|
|
||||||
"margin": "4px 0"
|
|
||||||
},
|
|
||||||
"binds": [{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "self",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/code_login.dspy'),
|
|
||||||
"params": {
|
|
||||||
"cell_no": cellphone,
|
|
||||||
"codeid": key,
|
|
||||||
"check_code": sms_code,
|
|
||||||
"selected_id": r.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"padding": "12px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "该手机号关联多个账号,请选择:",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"margin": "0 0 8px 0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
] + buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
# 新用户自动注册
|
|
||||||
d = await register_user(sor, udata)
|
|
||||||
if d['status'] == 'error':
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": d['data']['message']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
ownerid = await get_owner_orgid(sor, orgid)
|
|
||||||
await openCustomerAccounts(sor, ownerid, orgid)
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'{e}')
|
|
||||||
|
|
||||||
r = d['data']['user']
|
|
||||||
await remember_user(r.id, username=r.username, userorgid=r.orgid)
|
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "登录成功",
|
|
||||||
"message": f"{r.username} 欢迎"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "window.user_container",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/userinfo.ui')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "body.login_window",
|
|
||||||
"script": "this.destroy()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'code_login error: {e}')
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "系统错误",
|
|
||||||
"message": f"{e}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
debug(f'edit_profile.dspy: {params_kw=}')
|
|
||||||
|
|
||||||
userid = await get_user()
|
|
||||||
if userid is None:
|
|
||||||
return UiError(title='Error', message='You need login first')
|
|
||||||
|
|
||||||
db = DBPools()
|
|
||||||
dbname = get_module_dbname('rbac')
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
recs = await sor.R('users', {'id': userid})
|
|
||||||
if not recs:
|
|
||||||
return UiError(title='Error', message='User not found')
|
|
||||||
user = recs[0]
|
|
||||||
nick_name = user.nick_name or ''
|
|
||||||
email = user.email or ''
|
|
||||||
mobile = user.mobile or ''
|
|
||||||
address = user.address or ''
|
|
||||||
|
|
||||||
return {
|
|
||||||
"widgettype": "Form",
|
|
||||||
"options": {
|
|
||||||
"title": "完善个人信息",
|
|
||||||
"description": "请填写或更新您的个人信息",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "nick_name",
|
|
||||||
"type": "str",
|
|
||||||
"length": 255,
|
|
||||||
"uitype": "str",
|
|
||||||
"datatype": "str",
|
|
||||||
"required": False,
|
|
||||||
"label": "显示名",
|
|
||||||
"value": nick_name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"type": "str",
|
|
||||||
"length": 255,
|
|
||||||
"uitype": "email",
|
|
||||||
"datatype": "str",
|
|
||||||
"required": False,
|
|
||||||
"label": "邮件地址",
|
|
||||||
"value": email
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mobile",
|
|
||||||
"type": "str",
|
|
||||||
"length": 255,
|
|
||||||
"uitype": "tel",
|
|
||||||
"datatype": "str",
|
|
||||||
"required": False,
|
|
||||||
"label": "手机号",
|
|
||||||
"value": mobile
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "address",
|
|
||||||
"type": "str",
|
|
||||||
"length": 255,
|
|
||||||
"uitype": "str",
|
|
||||||
"datatype": "str",
|
|
||||||
"required": False,
|
|
||||||
"label": "地址",
|
|
||||||
"value": address
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "submit",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "self",
|
|
||||||
"options": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": entire_url('save_profile.dspy')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,22 +1,16 @@
|
|||||||
{% set sms_code_url = entire_url('/rbac/gen_sms_code.dspy') %}
|
|
||||||
{% set code_login_url = entire_url('/rbac/user/code_login.dspy') %}
|
|
||||||
{% set sms_register_url = entire_url('/rbac/user/sms_register.dspy') %}
|
|
||||||
{
|
{
|
||||||
"id": "login_window",
|
"id": "login_window",
|
||||||
"widgettype": "PopupWindow",
|
"widgettype": "PopupWindow",
|
||||||
"options": {
|
"options": {
|
||||||
"title": "欢迎登录",
|
"title": "登录/注册",
|
||||||
"i18n": true,
|
|
||||||
"css": "login-window",
|
|
||||||
"auto_open": true,
|
"auto_open": true,
|
||||||
"archor": "cc",
|
"anthor": "cc",
|
||||||
"cwidth": 26,
|
"cwidth": 22,
|
||||||
"cheight": 30
|
"cheight": 19
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "TabPanel",
|
"widgettype": "TabPanel",
|
||||||
"id": "login_tabs",
|
|
||||||
"options": {
|
"options": {
|
||||||
"tab_wide": "auto",
|
"tab_wide": "auto",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
@ -25,18 +19,22 @@
|
|||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"name": "userpasswd",
|
"name": "userpasswd",
|
||||||
"label": "密码登录",
|
"label": "用户密码",
|
||||||
"content": {
|
"content": {
|
||||||
"widgettype": "VScrollPanel",
|
|
||||||
"options": {"height": "100%"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Form",
|
"widgettype": "Form",
|
||||||
"options": {
|
"options": {
|
||||||
"cols": 1,
|
"cols": 1,
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "username", "label": "用户名", "uitype": "str"},
|
{
|
||||||
{"name": "password", "label": "密码", "uitype": "password"}
|
"name": "username",
|
||||||
|
"label": "用户名",
|
||||||
|
"uitype": "str"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"label": "密码",
|
||||||
|
"uitype": "password"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"binds": [
|
"binds": [
|
||||||
@ -52,134 +50,61 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phonecode",
|
"name": "checkcode",
|
||||||
"label": "手机登录",
|
"label": "手机验证码",
|
||||||
"content": {
|
"content": {
|
||||||
"widgettype": "VScrollPanel",
|
"widgettype": "Form",
|
||||||
"options": {"height": "100%"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"gap": "8px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
"options": {
|
||||||
"otext": "未注册的手机号将自动创建账号",
|
"toolbar": {
|
||||||
"i18n": true,
|
"tools": [
|
||||||
"css": "login-desc"
|
{
|
||||||
|
"name": "gen_code",
|
||||||
|
"label": "发送验证码"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "限中国国内手机",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "cell_no",
|
||||||
|
"label": "手机号",
|
||||||
|
"uitype": "str"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "Form",
|
"name": "codeid",
|
||||||
"id": "phone_form",
|
"uitype": "hide",
|
||||||
"options": {
|
"value": "{{uuid()}}"
|
||||||
"cols": 1,
|
},
|
||||||
"fields": [
|
{
|
||||||
{"name": "cell_no", "label": "手机号", "uitype": "str"},
|
"name": "check_code",
|
||||||
{"name": "codeid", "uitype": "hide", "value": ""},
|
"uitype": "str"
|
||||||
{"name": "check_code", "label": "验证码", "uitype": "str"}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"binds": [
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "gen_code",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"datawidget": "self",
|
||||||
|
"datamethod": "getValue",
|
||||||
|
"target": "self",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('../gen_sms_code.dspy')}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"wid": "self",
|
"wid": "self",
|
||||||
"event": "submit",
|
"event": "submit",
|
||||||
"actiontype": "urlwidget",
|
"actiontype": "urlwidget",
|
||||||
"target": "self",
|
"target": "self",
|
||||||
"options": {
|
"options": {
|
||||||
"method": "POST",
|
"url": "{{entire_url('code_login.dspy')}}"
|
||||||
"url": "{{code_login_url}}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"id": "gen_code_btn",
|
|
||||||
"options": {"label": "发送验证码", "i18n": true, "css": "sms-send-btn"},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "var form=bricks.getWidgetById('phone_form',bricks.app);if(!form)return;var cell=form._getValue().cell_no;if(!cell||cell.length<11){alert('请输入正确的手机号');return;}var btn=this;btn.disabled=true;btn.text_w&&btn.text_w.set_otext('发送中...');fetch('{{sms_code_url}}?_webbricks_=1&cellphone='+encodeURIComponent(cell)).then(function(r){return r.json()}).then(function(d){if(d.status==='ok'){var w=form.name_inputs['codeid'];if(w)w.setValue(d.data.key);btn.text_w&&btn.text_w.set_otext('已发送');var s=60;var t=setInterval(function(){s--;if(s<=0){clearInterval(t);btn.disabled=false;btn.text_w&&btn.text_w.set_otext('重新发送')}else btn.text_w&&btn.text_w.set_otext(s+'s')},1000)}else{alert(d.data.message||'发送验证码出错');btn.disabled=false;btn.text_w&&btn.text_w.set_otext('发送验证码')}}).catch(function(e){alert('网络错误: '+e);btn.disabled=false;btn.text_w&&btn.text_w.set_otext('发送验证码')})"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "register",
|
|
||||||
"label": "注册账号",
|
|
||||||
"content": {
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"gap": "8px", "height": "100%"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"otext": "手机号需短信验证后方可注册",
|
|
||||||
"i18n": true,
|
|
||||||
"css": "login-desc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VScrollPanel",
|
|
||||||
"options": {"height": "100%", "flex": "1"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Form",
|
|
||||||
"id": "register_form",
|
|
||||||
"options": {
|
|
||||||
"cols": 1,
|
|
||||||
"fields": [
|
|
||||||
{"name": "username", "label": "用户名", "uitype": "str"},
|
|
||||||
{"name": "mobile", "label": "手机号", "uitype": "str"},
|
|
||||||
{"name": "codeid", "uitype": "hide", "value": ""},
|
|
||||||
{"name": "check_code", "label": "短信验证码", "uitype": "str"},
|
|
||||||
{"name": "password", "label": "密码", "uitype": "password"},
|
|
||||||
{"name": "cfm_password", "label": "确认密码", "uitype": "password"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "submit",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "self",
|
|
||||||
"options": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": "{{sms_register_url}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"id": "reg_sms_btn",
|
|
||||||
"options": {"label": "发送验证码", "i18n": true, "css": "sms-send-btn"},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "var form=bricks.getWidgetById('register_form',bricks.app);if(!form)return;var cell=form._getValue().mobile;if(!cell||cell.length<11){alert('请输入正确的手机号');return;}var btn=this;btn.disabled=true;btn.text_w&&btn.text_w.set_otext('发送中...');fetch('{{sms_code_url}}?_webbricks_=1&cellphone='+encodeURIComponent(cell)).then(function(r){return r.json()}).then(function(d){if(d.status==='ok'){var w=form.name_inputs['codeid'];if(w)w.setValue(d.data.key);btn.text_w&&btn.text_w.set_otext('已发送');var s=60;var t=setInterval(function(){s--;if(s<=0){clearInterval(t);btn.disabled=false;btn.text_w&&btn.text_w.set_otext('重新发送')}else btn.text_w&&btn.text_w.set_otext(s+'s')},1000)}else{alert(d.data.message||'发送验证码出错');btn.disabled=false;btn.text_w&&btn.text_w.set_otext('发送验证码')}}).catch(function(e){alert('网络错误: '+e);btn.disabled=false;btn.text_w&&btn.text_w.set_otext('发送验证码')})"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,38 +1,9 @@
|
|||||||
await forget_user()
|
await forget_user()
|
||||||
return {
|
return {
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"padding": "24px",
|
|
||||||
"alignItems": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype":"Text",
|
"widgettype":"Text",
|
||||||
"options":{
|
"options":{
|
||||||
"otext":"logout success",
|
"otext":"logout success",
|
||||||
"i18n":True,
|
"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()"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,69 +1,18 @@
|
|||||||
debug(f'register.dspy: {params_kw=}')
|
debug(f'{params_kw=}')
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
dbname = get_module_dbname('rbac')
|
dbname = get_module_dbname('rbac')
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
data = await register_user(sor, params_kw)
|
data = await register_user(sor, params_kw)
|
||||||
data = DictObject(**data)
|
data = DictObject(**data)
|
||||||
if data.status == 'error':
|
|
||||||
debug(f"register error: {data.data.message}")
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": data.data.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user = data.data.user
|
|
||||||
orgid = user.orgid
|
|
||||||
try:
|
try:
|
||||||
|
if data['status'] == 'error':
|
||||||
|
debug(f"{data.data.message}")
|
||||||
|
return UiError(title='Error', message=data.data.message)
|
||||||
|
orgid = data.data.user.orgid
|
||||||
await openCustomerAccounts(sor, '0', orgid)
|
await openCustomerAccounts(sor, '0', orgid)
|
||||||
debug(f'{orgid} accounts opened')
|
debug(f'{orgid} accounts opened')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception(f'{e},{orgid=}')
|
exception(f'{e},{orgid=}')
|
||||||
|
|
||||||
# 注册成功后自动登录
|
return UiMessage(title="Success", message=f"register success {orgid}")
|
||||||
await remember_user(user.id, username=user.username, userorgid=user.orgid)
|
return UiError(title='Error', message="register failed")
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "注册成功",
|
|
||||||
"message": f"{user.username} 注册成功,已自动登录"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "window.user_container",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/userinfo.ui')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "body.login_window",
|
|
||||||
"script": "if(this.destroy) this.destroy()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "系统错误,请稍后重试"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
debug(f'save_profile.dspy: {params_kw=}')
|
|
||||||
|
|
||||||
userid = await get_user()
|
|
||||||
if userid is None:
|
|
||||||
return UiError(title='Error', message='You need login first')
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'id': userid,
|
|
||||||
'nick_name': params_kw.nick_name or '',
|
|
||||||
'email': params_kw.email or '',
|
|
||||||
'mobile': params_kw.mobile or '',
|
|
||||||
'address': params_kw.address or ''
|
|
||||||
}
|
|
||||||
|
|
||||||
db = DBPools()
|
|
||||||
dbname = get_module_dbname('rbac')
|
|
||||||
try:
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
await sor.U('users', data)
|
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "保存成功",
|
|
||||||
"message": "个人信息已更新"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('profile_updated')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'save_profile error: {e}')
|
|
||||||
return UiError(title='Error', message=f'保存失败: {e}')
|
|
||||||
@ -1,187 +0,0 @@
|
|||||||
# 短信验证注册 - 接收前端表单参数(username, mobile, codeid, check_code, password, cfm_password)
|
|
||||||
# 先验证短信码,通过后注册并自动登录
|
|
||||||
debug(f'sms_register.dspy: {params_kw=}')
|
|
||||||
|
|
||||||
username = params_kw.username
|
|
||||||
mobile = params_kw.mobile
|
|
||||||
key = params_kw.codeid
|
|
||||||
sms_code = params_kw.check_code
|
|
||||||
password = params_kw.password
|
|
||||||
cfm_password = params_kw.cfm_password
|
|
||||||
|
|
||||||
# 基本参数校验
|
|
||||||
if not username:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "请输入用户名"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not mobile:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "请输入手机号"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not password:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "请输入密码"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if password != cfm_password:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "两次输入的密码不一致"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if not key or not sms_code:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "请先发送并输入短信验证码"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证短信码
|
|
||||||
try:
|
|
||||||
ok = await sms_engine.check_sms_code(key, sms_code)
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'sms_register sms check error: {e}')
|
|
||||||
ok = False
|
|
||||||
|
|
||||||
if not ok:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"title": "验证失败",
|
|
||||||
"message": "短信验证码错误或已过期,请重新获取"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 短信验证通过,注册用户
|
|
||||||
db = DBPools()
|
|
||||||
dbname = get_module_dbname('rbac')
|
|
||||||
try:
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
# 检查手机号是否已注册
|
|
||||||
existing = await sor.R('users', {'mobile': mobile})
|
|
||||||
if existing:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "该手机号已注册,请直接登录"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查用户名是否已存在
|
|
||||||
existing_user = await sor.R('users', {'username': username})
|
|
||||||
if existing_user:
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "用户名已被占用"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 调用注册函数
|
|
||||||
reg_params = DictObject(
|
|
||||||
username=username,
|
|
||||||
mobile=mobile,
|
|
||||||
password=password,
|
|
||||||
cfm_password=cfm_password
|
|
||||||
)
|
|
||||||
data = await register_user(sor, reg_params)
|
|
||||||
data = DictObject(**data)
|
|
||||||
if data.status == 'error':
|
|
||||||
debug(f"sms_register error: {data.data.message}")
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": data.data.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user = data.data.user
|
|
||||||
orgid = user.orgid
|
|
||||||
try:
|
|
||||||
await openCustomerAccounts(sor, '0', orgid)
|
|
||||||
debug(f'{orgid} accounts opened')
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'{e},{orgid=}')
|
|
||||||
|
|
||||||
# 注册成功后自动登录
|
|
||||||
await remember_user(user.id, username=user.username, userorgid=user.orgid)
|
|
||||||
return {
|
|
||||||
"widgettype": "Message",
|
|
||||||
"options": {
|
|
||||||
"timeout": 3,
|
|
||||||
"auto_open": True,
|
|
||||||
"title": "注册成功",
|
|
||||||
"message": f"{user.username} 注册成功,已自动登录"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "window.user_container",
|
|
||||||
"options": {
|
|
||||||
"url": entire_url('/rbac/user/userinfo.ui')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "body.login_window",
|
|
||||||
"script": "this.destroy()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "dismissed",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
exception(f'sms_register error: {e}')
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "系统错误",
|
|
||||||
"message": f"注册失败: {e}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"widgettype": "Error",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5,
|
|
||||||
"title": "注册失败",
|
|
||||||
"message": "系统错误,请稍后重试"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
debug(f'{params_kw=}')
|
debug(f'{params_kw=}')
|
||||||
ns = {
|
ns = {
|
||||||
"username":params_kw.username,
|
"username":params_kw.username,
|
||||||
@ -12,10 +13,6 @@ async with db.sqlorContext(dbname) as sor:
|
|||||||
r = await sor.sqlExe('select * from users where username=${username}$', ns.copy())
|
r = await sor.sqlExe('select * from users where username=${username}$', ns.copy())
|
||||||
if len(r) == 0:
|
if len(r) == 0:
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
|
||||||
"data": {
|
|
||||||
"message": "user name or password error"
|
|
||||||
},
|
|
||||||
"widgettype":"Error",
|
"widgettype":"Error",
|
||||||
"options":{
|
"options":{
|
||||||
"timeout":3,
|
"timeout":3,
|
||||||
@ -34,10 +31,6 @@ async with db.sqlorContext(dbname) as sor:
|
|||||||
elapsed = (datetime.datetime.now() - stored).total_seconds()
|
elapsed = (datetime.datetime.now() - stored).total_seconds()
|
||||||
if elapsed < 300:
|
if elapsed < 300:
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
|
||||||
"data": {
|
|
||||||
"message": "Account locked due to too many failed login attempts. Please try again in 5 minutes."
|
|
||||||
},
|
|
||||||
"widgettype":"Error",
|
"widgettype":"Error",
|
||||||
"options":{
|
"options":{
|
||||||
"timeout":5,
|
"timeout":5,
|
||||||
@ -72,10 +65,6 @@ async with db.sqlorContext(dbname) as sor:
|
|||||||
else:
|
else:
|
||||||
msg = f"user name or password error ({3 - new_fail_count} attempts remaining)"
|
msg = f"user name or password error ({3 - new_fail_count} attempts remaining)"
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
|
||||||
"data": {
|
|
||||||
"message": msg
|
|
||||||
},
|
|
||||||
"widgettype":"Error",
|
"widgettype":"Error",
|
||||||
"options":{
|
"options":{
|
||||||
"timeout":3,
|
"timeout":3,
|
||||||
@ -93,12 +82,9 @@ async with db.sqlorContext(dbname) as sor:
|
|||||||
""", {'id': user.id, 'now': now_str})
|
""", {'id': user.id, 'now': now_str})
|
||||||
await remember_user(r[0].id, username=r[0].username, userorgid=r[0].orgid)
|
await remember_user(r[0].id, username=r[0].username, userorgid=r[0].orgid)
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
|
||||||
"data": { "user": r[0]},
|
|
||||||
"widgettype":"Message",
|
"widgettype":"Message",
|
||||||
"options":{
|
"options":{
|
||||||
"timeout":3,
|
"timeout":3,
|
||||||
"anchor": "cc",
|
|
||||||
"auto_open":True,
|
"auto_open":True,
|
||||||
"title":"Login",
|
"title":"Login",
|
||||||
"message":f"{r[0].username} Welcome back"
|
"message":f"{r[0].username} Welcome back"
|
||||||
@ -108,26 +94,21 @@ async with db.sqlorContext(dbname) as sor:
|
|||||||
"wid":"self",
|
"wid":"self",
|
||||||
"event":"dismissed",
|
"event":"dismissed",
|
||||||
"actiontype":"urlwidget",
|
"actiontype":"urlwidget",
|
||||||
"target":"window",
|
"target":"window.user_container",
|
||||||
"options":{
|
"options":{
|
||||||
"url":entire_url('/index.ui')
|
"url":entire_url('/rbac/user/userinfo.ui')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wid":"self",
|
"wid":"self",
|
||||||
"event":"opened",
|
"event":"dismissed",
|
||||||
"actiontype":"script",
|
"actiontype":"script",
|
||||||
"target":"window.login_window",
|
"target": f'body.login_window',
|
||||||
"script":"this.destroy()"
|
"script":"this.destroy()"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
|
||||||
"data": {
|
|
||||||
"message": "system error"
|
|
||||||
},
|
|
||||||
|
|
||||||
"widgettype":"Error",
|
"widgettype":"Error",
|
||||||
"options":{
|
"options":{
|
||||||
"timeout":3,
|
"timeout":3,
|
||||||
|
|||||||
25
wwwroot/user/userapikey/add_userapikey.dspy
Normal file
25
wwwroot/user/userapikey/add_userapikey.dspy
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
id = params_kw.id
|
||||||
|
if not id or len(id) > 32:
|
||||||
|
id = uuid()
|
||||||
|
ns['id'] = id
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
r = await sor.C('userapikey', ns.copy())
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"user_data":ns,
|
||||||
|
"title":"Add Success",
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Add Error",
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
wwwroot/user/userapikey/delete_userapikey.dspy
Normal file
24
wwwroot/user/userapikey/delete_userapikey.dspy
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
ns = {
|
||||||
|
'id':params_kw['id'],
|
||||||
|
}
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
r = await sor.D('userapikey', ns)
|
||||||
|
print('delete success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Success",
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print('Delete failed');
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Error",
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
74
wwwroot/user/userapikey/get_userapikey.dspy
Normal file
74
wwwroot/user/userapikey/get_userapikey.dspy
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
print(f'get_userapikey.dspy:{ns=}')
|
||||||
|
if not ns.get('page'):
|
||||||
|
ns['page'] = 1
|
||||||
|
if not ns.get('sort'):
|
||||||
|
|
||||||
|
ns['sort'] = 'id'
|
||||||
|
|
||||||
|
filterjson = params_kw.get('data_filter')
|
||||||
|
userid = await get_user()
|
||||||
|
ns['userid'] = userid
|
||||||
|
if not filterjson:
|
||||||
|
fields = [ f['name'] for f in [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "providerid",
|
||||||
|
"title": "\u4f9b\u5e94\u5546id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "customerid",
|
||||||
|
"title": "\u7528\u6237id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"default": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apikey",
|
||||||
|
"title": "api\u5bc6\u94a5",
|
||||||
|
"type": "str",
|
||||||
|
"length": 4000,
|
||||||
|
"default": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "secretkey",
|
||||||
|
"title": "\u9644\u5c5e\u5bc6\u94a5",
|
||||||
|
"type": "str",
|
||||||
|
"length": 4000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rfname",
|
||||||
|
"title": "\u51fd\u6570\u540d",
|
||||||
|
"type": "str",
|
||||||
|
"length": 400
|
||||||
|
}
|
||||||
|
] ]
|
||||||
|
filterjson = default_filterjson(fields, ns)
|
||||||
|
sql = '''select a.*, b.providerid_text, c.customerid_text
|
||||||
|
from (select y.* from users x, userapikey y where x.id=${userid}$ and x.orgid = y.customerid) a left join (select id as providerid,
|
||||||
|
orgname as providerid_text from organization where 1 = 1) b on a.providerid = b.providerid left join (select id as customerid,
|
||||||
|
orgname as customerid_text from organization where 1 = 1) c on a.customerid = c.customerid'''
|
||||||
|
|
||||||
|
if filterjson:
|
||||||
|
dbf = DBFilter(filterjson)
|
||||||
|
conds = dbf.gen(ns)
|
||||||
|
if conds:
|
||||||
|
ns.update(dbf.consts)
|
||||||
|
sql += ' and ' + conds
|
||||||
|
info(f'{ns=},{sql=}')
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
r = await sor.sqlPaging(sql, ns)
|
||||||
|
return r
|
||||||
|
return {
|
||||||
|
"total":0,
|
||||||
|
"rows":[]
|
||||||
|
}
|
||||||
126
wwwroot/user/userapikey/index.ui
Normal file
126
wwwroot/user/userapikey/index.ui
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"widgettype":"Tabular",
|
||||||
|
"options":{
|
||||||
|
|
||||||
|
|
||||||
|
"editable":{
|
||||||
|
"new_data_url":"{{entire_url('add_userapikey.dspy')}}",
|
||||||
|
"delete_data_url":"{{entire_url('delete_userapikey.dspy')}}",
|
||||||
|
"update_data_url":"{{entire_url('update_userapikey.dspy')}}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"data_url":"{{entire_url('./get_userapikey.dspy')}}",
|
||||||
|
"data_method":"GET",
|
||||||
|
"data_params":{{json.dumps(params_kw, indent=4)}},
|
||||||
|
"row_options":{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"browserfields":{
|
||||||
|
"excloud": [],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"editexclouded":[
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fields":[
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "providerid",
|
||||||
|
"title": "\u4f9b\u5e94\u5546id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 200,
|
||||||
|
"label": "\u4f9b\u5e94\u5546id",
|
||||||
|
"cwidth":16,
|
||||||
|
"uitype": "code",
|
||||||
|
"cwidth":18,
|
||||||
|
"valueField": "providerid",
|
||||||
|
"textField": "providerid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname",
|
||||||
|
"valueField": "providerid",
|
||||||
|
"textField": "providerid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "\/get_code.dspy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "customerid",
|
||||||
|
"title": "\u7528\u6237id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth":18,
|
||||||
|
"default": "0",
|
||||||
|
"label": "\u7528\u6237id",
|
||||||
|
"cwidth":16,
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "customerid",
|
||||||
|
"textField": "customerid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname",
|
||||||
|
"valueField": "customerid",
|
||||||
|
"textField": "customerid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "\/get_code.dspy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apikey",
|
||||||
|
"title": "api\u5bc6\u94a5",
|
||||||
|
"type": "str",
|
||||||
|
"length": 4000,
|
||||||
|
"default": "0",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "text",
|
||||||
|
"rows": 4,
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "api\u5bc6\u94a5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "secretkey",
|
||||||
|
"title": "\u9644\u5c5e\u5bc6\u94a5",
|
||||||
|
"type": "str",
|
||||||
|
"length": 4000,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "text",
|
||||||
|
"rows": 4,
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "\u9644\u5c5e\u5bc6\u94a5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rfname",
|
||||||
|
"title": "\u51fd\u6570\u540d",
|
||||||
|
"type": "str",
|
||||||
|
"length": 400,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "text",
|
||||||
|
"rows": 4,
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "\u51fd\u6570\u540d"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"page_rows":160,
|
||||||
|
"cache_limit":5
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
wwwroot/user/userapikey/update_userapikey.dspy
Normal file
22
wwwroot/user/userapikey/update_userapikey.dspy
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
r = await sor.U('userapikey', ns)
|
||||||
|
print('update success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Success",
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print('update failed');
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Error",
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,16 +32,6 @@
|
|||||||
"label":"我的角色",
|
"label":"我的角色",
|
||||||
"url":"{{entire_url('user/myrole.ui')}}"
|
"url":"{{entire_url('user/myrole.ui')}}"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name":"editprofile",
|
|
||||||
"label":"完善信息",
|
|
||||||
"url":"{{entire_url('user/edit_profile.dspy')}}",
|
|
||||||
"popup_options":{
|
|
||||||
"title":"完善个人信息",
|
|
||||||
"cwidth":22,
|
|
||||||
"height":"75%"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name":"logout",
|
"name":"logout",
|
||||||
"label":"签退",
|
"label":"签退",
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
if not params_kw.get('id'):
|
|
||||||
return {"widgettype":"Error","options":{"title":"Error","message":"no user selected","cwidth":16,"cheight":9,"timeout":3}}
|
|
||||||
|
|
||||||
dbname = get_module_dbname('rbac')
|
|
||||||
db = DBPools()
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
await sor.U('users', {'id': params_kw.id, 'user_status': '1'})
|
|
||||||
return {"widgettype":"Message","options":{"title":"Success","message":"user disabled","cwidth":16,"cheight":9,"timeout":3}}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
if not params_kw.get('id'):
|
|
||||||
return {"widgettype":"Error","options":{"title":"Error","message":"no user selected","cwidth":16,"cheight":9,"timeout":3}}
|
|
||||||
|
|
||||||
dbname = get_module_dbname('rbac')
|
|
||||||
db = DBPools()
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
await sor.U('users', {'id': params_kw.id, 'user_status': '0', 'login_fail_count': 0})
|
|
||||||
return {"widgettype":"Message","options":{"title":"Success","message":"user enabled","cwidth":16,"cheight":9,"timeout":3}}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user