diff --git a/app/sage.py b/app/sage.py index e52d552..bf02295 100644 --- a/app/sage.py +++ b/app/sage.py @@ -25,6 +25,7 @@ from discount.init import load_discount from harnessed_agent.init import load_harnessed_agent from harnessed_reasoning.init import load_harnessed_reasoning from hermes_web_cli.init import load_hermes_web_cli +from dashboard_for_sage.init import load_dashboard_for_sage from global_func import set_globalvariable from unipay.init import load_unipay from platformbiz.init import load_platformbiz @@ -56,6 +57,7 @@ def init(): load_discount() load_hermes_web_cli() load_smssend() + load_dashboard_for_sage() if __name__ == '__main__': webapp(init) diff --git a/build.sh b/build.sh index b88ef58..904e87f 100755 --- a/build.sh +++ b/build.sh @@ -26,7 +26,7 @@ do cd $m $cdir/py3/bin/pip install . done -for m in appbase rbac accounting llmage platformbiz msp cpcc unipay filemgr dapi uapi rag charge pricing discount harnessed_agent harnessed_reasoning +for m in appbase rbac accounting llmage platformbiz msp cpcc unipay filemgr dapi uapi rag charge pricing discount harnessed_agent harnessed_reasoning dashboard_for_sage do echo "install $m module..." cd $cdir/pkgs diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui index b53c3aa..591f9a9 100644 --- a/wwwroot/menu.ui +++ b/wwwroot/menu.ui @@ -10,6 +10,11 @@ "label": "主页", "url": "{{entire_url('public')}}" }, + { + "name": "dashboard", + "label": "数据看板", + "submenu": "{{entire_url('/dashboard_for_sage/menu.ui')}}" + }, { "name": "agent", "label": "代理", diff --git a/xlsx2json_models.py b/xlsx2json_models.py new file mode 100644 index 0000000..085f0c9 --- /dev/null +++ b/xlsx2json_models.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +将 Sage 相关模块 models/ 目录下的 .xlsx 文件转换为 .json 数据表定义文件。 + +用法(在 Sage 虚拟环境中运行): + python ~/repos/sage/xlsx2json_models.py # 转换所有模块 + python ~/repos/sage/xlsx2json_models.py llmage # 只转换 llmage + python ~/repos/sage/xlsx2json_models.py --dry-run # 预览 +""" +import sys +import os +import json +import argparse + +# Sage 虚拟环境路径 +sage_root = os.path.expanduser('~/repos/sage') +sys.path.insert(0, sage_root) +sys.path.insert(0, os.path.join(sage_root, 'py3/lib/python3.10/site-packages')) + +from appPublic.folderUtils import listFile +from xls2ddl.xlsxData import xlsxFactory, CRUDData + +# Sage 相关模块列表 +SAGE_MODULES = [ + 'appbase', 'rbac', 'accounting', 'llmage', 'platformbiz', + 'msp', 'cpcc', 'unipay', 'filemgr', 'dapi', 'uapi', + 'rag', 'charge', 'pricing', 'discount', 'hermes-web-cli', +] + + +def xlsx_to_json_dict(xlsx_path): + """将 xlsx 文件读取并转换为 json 格式的 dict。""" + d = xlsxFactory(xlsx_path) + if d is None: + return None + data = d.get_data() + + # 只保留目标字段 + result = {} + + # summary: 确保 primary 是 list + if 'summary' in data: + result['summary'] = [] + for s in data['summary']: + entry = {'name': s.get('name'), 'title': s.get('title')} + primary = s.get('primary', '') + if isinstance(primary, list): + entry['primary'] = primary + elif primary: + entry['primary'] = [p.strip() for p in str(primary).split(',')] + else: + entry['primary'] = [] + if s.get('catelog'): + entry['catelog'] = s['catelog'] + result['summary'].append(entry) + + # fields: 清理 None 值 + if 'fields' in data: + result['fields'] = [] + for f in data['fields']: + entry = {'name': f.get('name'), 'title': f.get('title'), 'type': f.get('type')} + if f.get('length') is not None: + entry['length'] = f['length'] + if f.get('nullable') is not None: + entry['nullable'] = f['nullable'] + if f.get('default') is not None: + entry['default'] = f['default'] + result['fields'].append(entry) + + # indexes: 从 validation 中的 idx 操作转换,或直接用 indexes sheet + if 'indexes' in data and data['indexes']: + result['indexes'] = data['indexes'] + elif 'validation' in data: + indexes = [] + for v in data['validation']: + if v.get('oper') == 'idx': + idx = {'name': v.get('name'), 'idxtype': 'index'} + val = v.get('value', '') + parts = val.split(':') + if len(parts) >= 2: + idx['idxtype'] = parts[0] + idx['idxfields'] = [f.strip() for f in parts[1].split(',')] + indexes.append(idx) + if indexes: + result['indexes'] = indexes + + # codes + if 'codes' in data and data['codes']: + result['codes'] = data['codes'] + + return result + + +def convert_module(module_name, dry_run=False): + """转换单个模块的 models 目录。""" + module_dir = os.path.expanduser(f'~/repos/{module_name}') + models_dir = os.path.join(module_dir, 'models') + + if not os.path.isdir(models_dir): + print(f" [SKIP] {module_name}: models/ directory not found") + return 0, 0 + + xlsx_files = [f for f in listFile(models_dir) if f.endswith('.xlsx')] + if not xlsx_files: + print(f" [SKIP] {module_name}: no .xlsx files") + return 0, 0 + + converted = 0 + skipped = 0 + + for xlsx_path in xlsx_files: + table_name = os.path.splitext(os.path.basename(xlsx_path))[0] + json_path = os.path.join(models_dir, f'{table_name}.json') + + # 跳过已经是 json 的文件 + if os.path.exists(json_path): + print(f" [EXIST] {table_name}.json already exists") + skipped += 1 + continue + + try: + result = xlsx_to_json_dict(xlsx_path) + if result is None: + print(f" [FAIL] {table_name}: could not read xlsx") + skipped += 1 + continue + + if dry_run: + print(f" [DRY] Would create {table_name}.json ({len(result.get('fields', []))} fields)") + converted += 1 + continue + + with open(json_path, 'w', encoding='utf-8') as f: + json.dump(result, f, ensure_ascii=False, indent=4) + + print(f" [OK] {table_name}.json ({len(result.get('fields', []))} fields)") + converted += 1 + + except Exception as e: + print(f" [FAIL] {table_name}: {e}") + skipped += 1 + + return converted, skipped + + +def main(): + parser = argparse.ArgumentParser(description='Convert models/*.xlsx to models/*.json') + parser.add_argument('modules', nargs='*', help='Module names to convert (default: all)') + parser.add_argument('--dry-run', action='store_true', help='Preview without writing files') + args = parser.parse_args() + + modules = args.modules if args.modules else SAGE_MODULES + dry_run = args.dry_run + + print("=" * 60) + print(" Sage Models: xlsx -> json 转换") + print("=" * 60) + print(f"Modules: {', '.join(modules)}") + print(f"Dry run: {dry_run}") + print() + + total_converted = 0 + total_skipped = 0 + + for mod in modules: + print(f"[{mod}]") + c, s = convert_module(mod, dry_run=dry_run) + total_converted += c + total_skipped += s + print() + + print("=" * 60) + print(f" 完成: 转换 {total_converted}, 跳过 {total_skipped}") + print("=" * 60) + + +if __name__ == '__main__': + main()