diff --git a/scripts/init_rbac_v2.py b/scripts/init_rbac_v2.py new file mode 100644 index 0000000..1fb208e --- /dev/null +++ b/scripts/init_rbac_v2.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""RAG Server RBAC 权限初始化 — 自动扫描 wwwroot, 幂等""" +import sys, os, asyncio, hashlib + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) + +from sqlor.dbpools import DBPools +from appPublic.jsonConfig import getConfig + +config = getConfig('.') +DBPools(config.databases) + +BASE = os.path.dirname(os.path.abspath(__file__)) +WWWROOT = os.path.join(BASE, '..', 'pkgs', 'rag', 'wwwroot') + +PUBLIC = [ + '/', '/index.ui', '/top.ui', '/user_menu.ui', '/shell_theme.css', + '/i18n_getmsgs', '/bricks/**', '/i18n/**', '/uapi/**', + '/rbac/user/login.ui', '/rbac/user/register.ui', + '/rbac/user/login.dspy', '/rbac/user/register.dspy', '/rbac/user/logout.dspy', + '/rbac/**', + '/api/status', '/api/engines', '/api/kb/list', +] +LOGINED = [ + '/api/search', '/api/doc/upload', '/api/doc/delete', + '/api/dir/create', '/api/dir/delete', '/api/dir/list', + '/api/tag/create', '/api/tag/list', '/api/tag/delete', + '/api/tag/assign', '/api/tag/unassign', '/api/tag/media_tags', '/api/tag/search', +] + +def pid(path): + return 'p_' + hashlib.md5(path.encode()).hexdigest()[:12] + +def discover_paths(root, prefix='/rag'): + paths = set() + for dirpath, _, filenames in os.walk(root): + rel = dirpath[len(root):] + '/' + paths.add(prefix + rel) + for f in filenames: + if f.endswith(('.dspy', '.ui')): + paths.add(prefix + rel + f) + return sorted(paths) + +async def main(): + db = DBPools() + async with db.sqlorContext('rag') as sor: + for rid, rname in [('any', 'any'), ('logined', 'logined')]: + await sor.sqlExe( + f"INSERT IGNORE INTO role (id, orgtypeid, name) VALUES ('{rid}', '', '{rname}')", {}) + + for path in PUBLIC: + p = pid(path) + await sor.sqlExe(f"INSERT IGNORE INTO permission (id, path, name) VALUES ('{p}', '{path}', 'RAG')", {}) + await sor.sqlExe(f"INSERT IGNORE INTO rolepermission (id, roleid, permid) VALUES ('rp_{p}', 'any', '{p}')", {}) + + all_pages = discover_paths(WWWROOT) + for path in LOGINED + all_pages: + p = pid(path) + await sor.sqlExe(f"INSERT IGNORE INTO permission (id, path, name) VALUES ('{p}', '{path}', 'RAG')", {}) + await sor.sqlExe(f"INSERT IGNORE INTO rolepermission (id, roleid, permid) VALUES ('rp_{p}', 'logined', '{p}')", {}) + + print(f"OK: {len(PUBLIC)} public + {len(LOGINED) + len(all_pages)} logined") + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/test_rag.sh b/test_rag.sh new file mode 100755 index 0000000..b4b3ce3 --- /dev/null +++ b/test_rag.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# RAG Server smoke test — run after any change +# Usage: bash test_rag.sh [host] +HOST="${1:-http://localhost:9181}" +ADMIN="${2:-admin}" +PASS="${3:-admin123}" + +RED='\033[0;31m'; GREEN='\033[0;32m'; NC='\033[0m' +errors=0 + +check() { local desc="$1" code="$2" expect="$3" + if [ "$code" = "$expect" ]; then echo -e " ${GREEN}OK${NC} $desc"; else echo -e " ${RED}FAIL${NC} $desc (got $code, want $expect)"; errors=$((errors+1)); fi +} + +echo "=== RAG Server Smoke Test ===" + +# 1. Public endpoints (no login) +echo "-- Public --" +check "health" "$(curl -s -o /dev/null -w '%{http_code}' "$HOST/api/status")" 200 +check "no login kb" "$(curl -s -o /dev/null -w '%{http_code}' "$HOST/rag/knowledge_bases_list/index.ui")" 401 + +# 2. Login +S=$(curl -s -D - -X POST "$HOST/rbac/user/up_login.dspy" -d "username=$ADMIN&password=$PASS" | grep -oP 'AIOHTTP_SESSION=\K[^;]+' | head -1) +if [ -z "$S" ]; then echo -e " ${RED}FAIL${NC} login"; exit 1; fi +echo -e " ${GREEN}OK${NC} login" + +# 3. Logged-in endpoints +echo "-- Logined --" + +check "index.ui" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/index.ui")" 200 +check "detail.ui" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/detail.ui?kb_id=41c9f1cd45e0")" 200 +check "get_tree_data" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/get_tree_data.dspy?kb_id=41c9f1cd45e0")" 200 +check "file_list" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/file_list.dspy?kb_id=41c9f1cd45e0&id=__root__")" 200 +check "storage_card" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/storage_card.dspy?_webbricks_=1")" 200 +check "new_kb_form" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/new_kb_form.ui")" 200 +check "kb_list" "$(curl -s -o /dev/null -w '%{http_code}' -b "AIOHTTP_SESSION=$S" "$HOST/api/kb/list")" 200 + +# 4. Verify storage shows real data +used=$(curl -s -b "AIOHTTP_SESSION=$S" "$HOST/rag/knowledge_bases_list/storage_card.dspy?_webbricks_=1" | grep -oP '已用 \K[\d.]+') +if [ "$used" != "0.0" ] && [ -n "$used" ]; then + echo -e " ${GREEN}OK${NC} storage shows ${used}MB" +else + echo -e " ${RED}FAIL${NC} storage shows $used (want >0)" + errors=$((errors+1)) +fi + +echo "=== $errors errors ===" +exit $errors diff --git a/wwwroot/rag/kb_buttons.dspy b/wwwroot/rag/kb_buttons.dspy deleted file mode 100644 index fb9b87a..0000000 --- a/wwwroot/rag/kb_buttons.dspy +++ /dev/null @@ -1,18 +0,0 @@ -"""返回知识库选择按钮列表""" -env = request._run_ns -userorgid = await env.get_userorgid() -async with get_sor_context(env, 'rag') as sor: - sql = "SELECT id, name FROM knowledge_bases WHERE org_id IS NULL OR org_id=${org_id}$ ORDER BY name" - recs = await sor.sqlExe(sql, {"org_id": userorgid}) - -buttons = [] -for r in recs: - buttons.append({ - "widgettype": "Text", - "options": {"text": str(r.name), "cfontsize": 12, "padding": "4px 12px", - "bgcolor": "#eee", "color": "#333", "borderRadius": "4px", "css": "clickable kb-btn"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", - "script": f"var f=document.querySelector('[name=kb_id]');if(f)f.value='{r.id}';var btns=document.querySelectorAll('.kb-btn');for(var i=0;i