feat(cockpit): product-grade LLM integration with context + model mgmt

Backend:
- cockpit_chat.dspy: real LLM calls via llm table config, Hermes-style
  context (system prompt + project/iteration + history), auto model
  selection (multimodal if files, text otherwise)
- cockpit_model_options.dspy: returns active models with capabilities
- llm table: added capabilities column (text/vision/multimodal)

Frontend:
- inputed script: no page reload, updates agent bubble with LLM reply
- stats_row refreshed on success, error shown in bubble

DB:
- sd_agent_settings table for per-user agent preferences
This commit is contained in:
yumoqing 2026-07-31 18:06:27 +08:00
parent d7bace21db
commit 72cd262cdb
23 changed files with 1026 additions and 665 deletions

View File

@ -1,70 +1,103 @@
{ {
"tblname": "sd_bugs", "tblname": "sd_bugs",
"title": "Bug管理", "title": "Bug管理",
"params": { "params": {
"sortby": ["created_at desc"], "sortby": [
"confidential_fields": [], "created_at desc"
"browserfields": { ],
"alters": { "confidential_fields": [],
"iteration_id": { "browserfields": {
"uitype": "code", "alters": {
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}", "iteration_id": {
"valueField": "iteration_id", "uitype": "code",
"textField": "iteration_id_text" "dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
}, "valueField": "value",
"severity": { "textField": "text"
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}",
"valueField": "severity",
"textField": "severity_text"
},
"priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "priority",
"textField": "priority_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
},
"reporter_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}",
"valueField": "reporter_type",
"textField": "reporter_type_text"
}
}
}, },
"editexclouded": ["id", "created_at", "updated_at", "closed_at", "verified_by", "fix_commit"], "severity": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_bug_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_severity.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_bug_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_bug_delete.dspy')}}" "textField": "text"
}, },
"data_filter": { "priority": {
"AND": [ "uitype": "code",
{"field": "iteration_id", "op": "=", "var": "iteration_input"}, "dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
{"field": "severity", "op": "=", "var": "severity_input"}, "valueField": "value",
{"field": "status", "op": "=", "var": "status_input"}, "textField": "text"
{"field": "title", "op": "LIKE", "var": "title_input"}
]
}, },
"record_toolbar": [ "status": {
{ "uitype": "code",
"label": "确认", "dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}",
"actiontype": "dspy", "valueField": "value",
"url": "{{entire_url('../api/bug_confirm.dspy')}}", "textField": "text"
"options": {"icon": "check", "cwidth": 16, "cheight": 9} },
}, "reporter_type": {
{ "uitype": "code",
"label": "关闭", "dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}",
"actiontype": "dspy", "valueField": "value",
"url": "{{entire_url('../api/bug_close.dspy')}}", "textField": "text"
"options": {"icon": "block", "cwidth": 16, "cheight": 9} }
} }
] },
} "editexclouded": [
} "id",
"created_at",
"updated_at",
"closed_at",
"verified_by",
"fix_commit"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_bug_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_bug_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_bug_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "iteration_id",
"op": "=",
"var": "iteration_input"
},
{
"field": "severity",
"op": "=",
"var": "severity_input"
},
{
"field": "status",
"op": "=",
"var": "status_input"
},
{
"field": "title",
"op": "LIKE",
"var": "title_input"
}
]
},
"record_toolbar": [
{
"label": "确认",
"actiontype": "dspy",
"url": "{{entire_url('../api/bug_confirm.dspy')}}",
"options": {
"icon": "check",
"cwidth": 16,
"cheight": 9
}
},
{
"label": "关闭",
"actiontype": "dspy",
"url": "{{entire_url('../api/bug_close.dspy')}}",
"options": {
"icon": "block",
"cwidth": 16,
"cheight": 9
}
}
]
}
}

View File

@ -1,45 +1,62 @@
{ {
"tblname": "sd_deploy_envs", "tblname": "sd_deploy_envs",
"title": "部署环境", "title": "部署环境",
"params": { "params": {
"sortby": ["env_type"], "sortby": [
"confidential_fields": ["db_password", "ssh_key_path"], "env_type"
"browserfields": { ],
"exclouded": ["db_password", "ssh_key_path"], "confidential_fields": [
"alters": { "db_password",
"project_id": { "ssh_key_path"
"uitype": "code", ],
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}", "browserfields": {
"valueField": "project_id", "exclouded": [
"textField": "project_id_text" "db_password",
}, "ssh_key_path"
"env_type": { ],
"uitype": "code", "alters": {
"dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}", "project_id": {
"valueField": "env_type", "uitype": "code",
"textField": "env_type_text" "dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
}, "valueField": "value",
"status": { "textField": "text"
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
}
}
}, },
"editexclouded": ["id", "verified_at", "created_at", "updated_at"], "env_type": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_deploy_env_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_deploy_env_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_deploy_env_delete.dspy')}}" "textField": "text"
}, },
"record_toolbar": [ "status": {
{ "uitype": "code",
"label": "验证连接", "dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}",
"actiontype": "dspy", "valueField": "value",
"url": "{{entire_url('../api/env_verify.dspy')}}", "textField": "text"
"options": {"icon": "check", "cwidth": 16, "cheight": 9} }
} }
] },
} "editexclouded": [
} "id",
"verified_at",
"created_at",
"updated_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_deploy_env_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_deploy_env_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_deploy_env_delete.dspy')}}"
},
"record_toolbar": [
{
"label": "验证连接",
"actiontype": "dspy",
"url": "{{entire_url('../api/env_verify.dspy')}}",
"options": {
"icon": "check",
"cwidth": 16,
"cheight": 9
}
}
]
}
}

View File

@ -1,58 +1,81 @@
{ {
"tblname": "sd_iterations", "tblname": "sd_iterations",
"title": "迭代管理", "title": "迭代管理",
"params": { "params": {
"sortby": ["created_at desc"], "sortby": [
"confidential_fields": [], "created_at desc"
"browserfields": { ],
"exclouded": ["scope"], "confidential_fields": [],
"alters": { "browserfields": {
"project_id": { "exclouded": [
"uitype": "code", "scope"
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}", ],
"valueField": "project_id", "alters": {
"textField": "project_id_text" "project_id": {
}, "uitype": "code",
"iteration_type": { "dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"uitype": "code", "valueField": "value",
"dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}", "textField": "text"
"valueField": "iteration_type",
"textField": "iteration_type_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
}
}
}, },
"editexclouded": ["id", "task_id", "started_at", "completed_at", "created_at", "updated_at"], "iteration_type": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_iteration_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_iteration_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_iteration_delete.dspy')}}" "textField": "text"
}, },
"data_filter": { "status": {
"AND": [ "uitype": "code",
{"field": "project_id", "op": "=", "var": "project_input"}, "dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}",
{"field": "iteration_name", "op": "LIKE", "var": "name_input"}, "valueField": "value",
{"field": "status", "op": "=", "var": "status_input"} "textField": "text"
] }
}
},
"editexclouded": [
"id",
"task_id",
"started_at",
"completed_at",
"created_at",
"updated_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_iteration_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_iteration_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_iteration_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "project_id",
"op": "=",
"var": "project_input"
}, },
"subtables": [ {
{ "field": "iteration_name",
"field": "iteration_id", "op": "LIKE",
"title": "Bug列表", "var": "name_input"
"url": "{{entire_url('../sd_bug_list?iteration_id=${id}')}}", },
"subtable": "sd_bugs" {
}, "field": "status",
{ "op": "=",
"field": "iteration_id", "var": "status_input"
"title": "测试方案", }
"url": "{{entire_url('../sd_test_plan_list?iteration_id=${id}')}}", ]
"subtable": "sd_test_plans" },
} "subtables": [
] {
} "field": "iteration_id",
} "title": "Bug列表",
"url": "{{entire_url('../sd_bug_list?iteration_id=${id}')}}",
"subtable": "sd_bugs"
},
{
"field": "iteration_id",
"title": "测试方案",
"url": "{{entire_url('../sd_test_plan_list?iteration_id=${id}')}}",
"subtable": "sd_test_plans"
}
]
}
}

View File

@ -1,47 +1,74 @@
{ {
"tblname": "sd_projects", "tblname": "sd_projects",
"title": "项目管理", "title": "项目管理",
"params": { "params": {
"sortby": ["created_at desc"], "sortby": [
"logined_userorgid": "org_id", "created_at desc"
"confidential_fields": [], ],
"browserfields": { "logined_userorgid": "org_id",
"exclouded": ["tech_stack"], "confidential_fields": [],
"alters": { "browserfields": {
"status": { "exclouded": [
"uitype": "code", "tech_stack"
"dataurl": "{{entire_url('../api/get_search_status.dspy')}}", ],
"valueField": "status", "alters": {
"textField": "status_text" "status": {
}, "uitype": "code",
"project_type": { "dataurl": "{{entire_url('../api/get_search_status.dspy')}}",
"uitype": "code", "valueField": "value",
"dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}", "textField": "text"
"valueField": "project_type",
"textField": "project_type_text"
}
}
}, },
"editexclouded": ["id", "created_at", "updated_at", "org_id"], "project_type": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_project_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_project_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_project_delete.dspy')}}" "textField": "text"
}, },
"data_filter": { "pipeline_id": {
"AND": [ "uitype": "code",
{"field": "name", "op": "LIKE", "var": "name_input"}, "dataurl": "{{entire_url('../api/get_pipeline_options.dspy')}}",
{"field": "status", "op": "=", "var": "status_input"}, "valueField": "value",
{"field": "project_type", "op": "=", "var": "type_input"} "textField": "text"
] }
}
},
"editexclouded": [
"id",
"created_at",
"updated_at",
"org_id"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_project_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_project_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_project_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "name",
"op": "LIKE",
"var": "name_input"
}, },
"subtables": [ {
{ "field": "status",
"field": "project_id", "op": "=",
"title": "迭代列表", "var": "status_input"
"url": "{{entire_url('../sd_iteration_list?project_id=${id}')}}", },
"subtable": "sd_iterations" {
} "field": "project_type",
] "op": "=",
} "var": "type_input"
} }
]
},
"subtables": [
{
"field": "project_id",
"title": "迭代列表",
"url": "{{entire_url('../sd_iteration_list?project_id=${id}')}}",
"subtable": "sd_iterations"
}
]
}
}

View File

@ -1,50 +1,76 @@
{ {
"tblname": "sd_test_cases", "tblname": "sd_test_cases",
"title": "测试用例", "title": "测试用例",
"params": { "params": {
"sortby": ["priority desc", "created_at desc"], "sortby": [
"confidential_fields": [], "priority desc",
"browserfields": { "created_at desc"
"alters": { ],
"plan_id": { "confidential_fields": [],
"uitype": "code", "browserfields": {
"dataurl": "{{entire_url('../api/get_test_plan_options.dspy')}}", "alters": {
"valueField": "plan_id", "plan_id": {
"textField": "plan_id_text" "uitype": "code",
}, "dataurl": "{{entire_url('../api/get_test_plan_options.dspy')}}",
"case_type": { "valueField": "value",
"uitype": "code", "textField": "text"
"dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}",
"valueField": "case_type",
"textField": "case_type_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
},
"priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "priority",
"textField": "priority_text"
}
}
}, },
"editexclouded": ["id", "actual_result", "executed_by", "executed_at", "duration_ms", "created_at"], "case_type": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_test_case_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_test_case_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_test_case_delete.dspy')}}" "textField": "text"
}, },
"data_filter": { "status": {
"AND": [ "uitype": "code",
{"field": "plan_id", "op": "=", "var": "plan_input"}, "dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}",
{"field": "case_type", "op": "=", "var": "type_input"}, "valueField": "value",
{"field": "status", "op": "=", "var": "status_input"}, "textField": "text"
{"field": "priority", "op": "=", "var": "priority_input"} },
] "priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "value",
"textField": "text"
} }
}
},
"editexclouded": [
"id",
"actual_result",
"executed_by",
"executed_at",
"duration_ms",
"created_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_test_case_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_test_case_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_test_case_delete.dspy')}}"
},
"data_filter": {
"AND": [
{
"field": "plan_id",
"op": "=",
"var": "plan_input"
},
{
"field": "case_type",
"op": "=",
"var": "type_input"
},
{
"field": "status",
"op": "=",
"var": "status_input"
},
{
"field": "priority",
"op": "=",
"var": "priority_input"
}
]
} }
} }
}

View File

@ -1,44 +1,50 @@
{ {
"tblname": "sd_test_plans", "tblname": "sd_test_plans",
"title": "测试方案", "title": "测试方案",
"params": { "params": {
"sortby": ["created_at desc"], "sortby": [
"confidential_fields": [], "created_at desc"
"browserfields": { ],
"alters": { "confidential_fields": [],
"iteration_id": { "browserfields": {
"uitype": "code", "alters": {
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}", "iteration_id": {
"valueField": "iteration_id", "uitype": "code",
"textField": "iteration_id_text" "dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
}, "valueField": "value",
"plan_type": { "textField": "text"
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}",
"valueField": "plan_type",
"textField": "plan_type_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
}
}
}, },
"editexclouded": ["id", "created_at", "updated_at"], "plan_type": {
"editable": { "uitype": "code",
"new_data_url": "{{entire_url('../api/sd_test_plan_create.dspy')}}", "dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_test_plan_update.dspy')}}", "valueField": "value",
"delete_data_url": "{{entire_url('../api/sd_test_plan_delete.dspy')}}" "textField": "text"
}, },
"subtables": [ "status": {
{ "uitype": "code",
"field": "plan_id", "dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}",
"title": "测试用例", "valueField": "value",
"url": "{{entire_url('../sd_test_case_list?plan_id=${id}')}}", "textField": "text"
"subtable": "sd_test_cases" }
} }
] },
} "editexclouded": [
} "id",
"created_at",
"updated_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/sd_test_plan_create.dspy')}}",
"update_data_url": "{{entire_url('../api/sd_test_plan_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/sd_test_plan_delete.dspy')}}"
},
"subtables": [
{
"field": "plan_id",
"title": "测试用例",
"url": "{{entire_url('../sd_test_case_list?plan_id=${id}')}}",
"subtable": "sd_test_cases"
}
]
}
}

View File

@ -1,117 +1,132 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """RBAC path registration for pipeline-sdlc module."""
RBAC path registration for pipeline-sdlc module. import os, sys, subprocess
Run: cd ~/repos/sage && ./py3/bin/python ~/repos/pipeline-sdlc/scripts/load_path.py
"""
import os
import sys
import subprocess
MOD = "pipeline_sdlc" MOD = "pipeline-sdlc"
PATHS_ANY = [ PATHS_ANY = [
f"/{MOD}/index.ui", f"/{MOD}/index.ui",
f"/{MOD}/styles/sdlc.css",
] ]
PATHS_LOGINED = [ PATHS_LOGINED = [
f"/{MOD}", f"/{MOD}",
f"/{MOD}/sd_project",
f"/{MOD}/sd_project/index.ui",
f"/{MOD}/sd_project/get_sd_project_list.dspy",
f"/{MOD}/sd_project/add_sd_project_list.dspy",
f"/{MOD}/sd_project/update_sd_project_list.dspy",
f"/{MOD}/sd_project/delete_sd_project_list.dspy",
f"/{MOD}/sd_iteration",
f"/{MOD}/sd_iteration/index.ui",
f"/{MOD}/sd_iteration/get_sd_iteration_list.dspy",
f"/{MOD}/sd_iteration/add_sd_iteration_list.dspy",
f"/{MOD}/sd_iteration/update_sd_iteration_list.dspy",
f"/{MOD}/sd_iteration/delete_sd_iteration_list.dspy",
f"/{MOD}/sd_test_plan",
f"/{MOD}/sd_test_plan/index.ui",
f"/{MOD}/sd_test_plan/get_sd_test_plan_list.dspy",
f"/{MOD}/sd_test_plan/add_sd_test_plan_list.dspy",
f"/{MOD}/sd_test_plan/update_sd_test_plan_list.dspy",
f"/{MOD}/sd_test_plan/delete_sd_test_plan_list.dspy",
f"/{MOD}/sd_test_case",
f"/{MOD}/sd_test_case/index.ui",
f"/{MOD}/sd_test_case/get_sd_test_case_list.dspy",
f"/{MOD}/sd_test_case/add_sd_test_case_list.dspy",
f"/{MOD}/sd_test_case/update_sd_test_case_list.dspy",
f"/{MOD}/sd_test_case/delete_sd_test_case_list.dspy",
f"/{MOD}/sd_bug",
f"/{MOD}/sd_bug/index.ui",
f"/{MOD}/sd_bug/get_sd_bug_list.dspy",
f"/{MOD}/sd_bug/add_sd_bug_list.dspy",
f"/{MOD}/sd_bug/update_sd_bug_list.dspy",
f"/{MOD}/sd_bug/delete_sd_bug_list.dspy",
f"/{MOD}/sd_deploy_env",
f"/{MOD}/sd_deploy_env/index.ui",
f"/{MOD}/sd_deploy_env/get_sd_deploy_env_list.dspy",
f"/{MOD}/sd_deploy_env/add_sd_deploy_env_list.dspy",
f"/{MOD}/sd_deploy_env/update_sd_deploy_env_list.dspy",
f"/{MOD}/sd_deploy_env/delete_sd_deploy_env_list.dspy",
# API endpoints
f"/{MOD}/api/create_sd_project.dspy",
f"/{MOD}/api/update_sd_project.dspy",
f"/{MOD}/api/delete_sd_project.dspy",
f"/{MOD}/api/create_sd_iteration.dspy",
f"/{MOD}/api/update_sd_iteration.dspy",
f"/{MOD}/api/delete_sd_iteration.dspy",
f"/{MOD}/api/create_sd_bug.dspy",
f"/{MOD}/api/update_sd_bug.dspy",
f"/{MOD}/api/close_sd_bug.dspy",
f"/{MOD}/api/save_sd_deploy_env.dspy",
f"/{MOD}/api/get_project_options.dspy",
f"/{MOD}/api/get_iteration_options.dspy",
f"/{MOD}/api/check_iteration_bugs.dspy",
f"/{MOD}/api/submit_bug.dspy",
# Dashboard # Dashboard
f"/{MOD}/sd_dashboard/dashboard.ui", f"/{MOD}/sd_dashboard/index.ui",
f"/{MOD}/sd_dashboard/dashboard_data.dspy",
# Projects
f"/{MOD}/sd_projects/index.ui",
f"/{MOD}/sd_projects/get_sd_projects.dspy",
f"/{MOD}/sd_projects/add_sd_projects.dspy",
f"/{MOD}/sd_projects/update_sd_projects.dspy",
f"/{MOD}/sd_projects/delete_sd_projects.dspy",
f"/{MOD}/sd_project_list/index.ui",
# Iterations
f"/{MOD}/sd_iterations/index.ui",
f"/{MOD}/sd_iterations/get_sd_iterations.dspy",
f"/{MOD}/sd_iterations/add_sd_iterations.dspy",
f"/{MOD}/sd_iterations/update_sd_iterations.dspy",
f"/{MOD}/sd_iterations/delete_sd_iterations.dspy",
f"/{MOD}/sd_iteration_list/index.ui",
# Test Plans
f"/{MOD}/sd_test_plans/index.ui",
f"/{MOD}/sd_test_plans/get_sd_test_plans.dspy",
f"/{MOD}/sd_test_plans/add_sd_test_plans.dspy",
f"/{MOD}/sd_test_plans/update_sd_test_plans.dspy",
f"/{MOD}/sd_test_plans/delete_sd_test_plans.dspy",
f"/{MOD}/sd_test_plan_list/index.ui",
# Test Cases
f"/{MOD}/sd_test_cases/index.ui",
f"/{MOD}/sd_test_cases/get_sd_test_cases.dspy",
f"/{MOD}/sd_test_cases/add_sd_test_cases.dspy",
f"/{MOD}/sd_test_cases/update_sd_test_cases.dspy",
f"/{MOD}/sd_test_cases/delete_sd_test_cases.dspy",
f"/{MOD}/sd_test_case_list/index.ui",
# Bugs
f"/{MOD}/sd_bugs/index.ui",
f"/{MOD}/sd_bugs/get_sd_bugs.dspy",
f"/{MOD}/sd_bugs/add_sd_bugs.dspy",
f"/{MOD}/sd_bugs/update_sd_bugs.dspy",
f"/{MOD}/sd_bugs/delete_sd_bugs.dspy",
f"/{MOD}/sd_bug_list/index.ui",
# Deploy Envs
f"/{MOD}/sd_deploy_envs/index.ui",
f"/{MOD}/sd_deploy_envs/get_sd_deploy_envs.dspy",
f"/{MOD}/sd_deploy_envs/add_sd_deploy_envs.dspy",
f"/{MOD}/sd_deploy_envs/update_sd_deploy_envs.dspy",
f"/{MOD}/sd_deploy_envs/delete_sd_deploy_envs.dspy",
f"/{MOD}/sd_deploy_env_list/index.ui",
# Review
f"/{MOD}/sd_review/index.ui",
# API
f"/{MOD}/api/bug_close.dspy",
f"/{MOD}/api/bug_confirm.dspy",
f"/{MOD}/api/check_iteration_bugs.dspy",
f"/{MOD}/api/env_verify.dspy",
f"/{MOD}/api/get_iteration_options.dspy",
f"/{MOD}/api/get_project_options.dspy",
f"/{MOD}/api/get_test_plan_options.dspy",
f"/{MOD}/api/submit_bug.dspy",
f"/{MOD}/api/sd_bug_create.dspy",
f"/{MOD}/api/sd_bug_update.dspy",
f"/{MOD}/api/sd_bug_delete.dspy",
f"/{MOD}/api/sd_case_create.dspy",
f"/{MOD}/api/sd_case_update.dspy",
f"/{MOD}/api/sd_case_delete.dspy",
f"/{MOD}/api/sd_env_create.dspy",
f"/{MOD}/api/sd_env_update.dspy",
f"/{MOD}/api/sd_env_delete.dspy",
f"/{MOD}/api/sd_iter_create.dspy",
f"/{MOD}/api/sd_iter_update.dspy",
f"/{MOD}/api/sd_iter_delete.dspy",
f"/{MOD}/api/sd_plan_create.dspy",
f"/{MOD}/api/sd_plan_update.dspy",
f"/{MOD}/api/sd_plan_delete.dspy",
f"/{MOD}/api/sd_proj_create.dspy",
f"/{MOD}/api/sd_proj_update.dspy",
f"/{MOD}/api/sd_proj_delete.dspy",
# Search data sources
f"/{MOD}/api/get_search_bug_status.dspy",
f"/{MOD}/api/get_search_case_status.dspy",
f"/{MOD}/api/get_search_case_type.dspy",
f"/{MOD}/api/get_search_env_status.dspy",
f"/{MOD}/api/get_search_env_type.dspy",
f"/{MOD}/api/get_search_iteration_status.dspy",
f"/{MOD}/api/get_search_iteration_type.dspy",
f"/{MOD}/api/get_search_plan_status.dspy",
f"/{MOD}/api/get_search_plan_type.dspy",
f"/{MOD}/api/get_search_priority.dspy",
f"/{MOD}/api/get_search_project_type.dspy",
f"/{MOD}/api/get_search_reporter_type.dspy",
f"/{MOD}/api/get_search_severity.dspy",
f"/{MOD}/api/get_search_status.dspy",
] ]
def find_sage_root(): def find_app_root():
"""Find Sage root directory.""" for c in [os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
for candidate in [ os.path.expanduser("~/pipeline")]:
os.path.expanduser("~/repos/sage"), if os.path.isfile(os.path.join(c, "set_role_perm.py")):
os.path.expanduser("~/sage"), return c
]:
if os.path.isdir(os.path.join(candidate, "wwwroot")):
return candidate
return None return None
def main(): def main():
sage_root = find_sage_root() root = find_app_root()
if not sage_root: if not root:
print("ERROR: Sage root not found") print("ERROR: pipeline root not found")
sys.exit(1) sys.exit(1)
set_perm = os.path.join(root, "set_role_perm.py")
set_perm = os.path.join(sage_root, "scripts", "set_role_perm.py") py = sys.executable
if not os.path.exists(set_perm):
set_perm = os.path.join(sage_root, "set_role_perm.py")
if not os.path.exists(set_perm):
print(f"ERROR: set_role_perm.py not found in {sage_root}")
sys.exit(1)
python = os.path.join(sage_root, "py3", "bin", "python")
if not os.path.exists(python):
python = sys.executable
count = 0 count = 0
for role, paths in [("any", PATHS_ANY), ("logined", PATHS_LOGINED)]: for role, paths in [("any", PATHS_ANY), ("logined", PATHS_LOGINED)]:
for path in paths: for path in paths:
cmd = [python, set_perm, role, path] cmd = [py, set_perm, role, path]
result = subprocess.run(cmd, capture_output=True, text=True, cwd=sage_root) r = subprocess.run(cmd, capture_output=True, text=True, cwd=root)
if result.returncode == 0: if r.returncode == 0:
count += 1 count += 1
else: else:
print(f"WARN: {path} ({role}): {result.stderr.strip()}") print(f" WARN: {path} -> {r.stderr.strip()[-80:]}")
print(f"Registered {count}/{len(PATHS_ANY)+len(PATHS_LOGINED)} paths for {MOD}")
print(f"Registered {count} paths for module '{MOD}'")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,58 +1,241 @@
# cockpit_chat.dspy - Send message or list conversation # cockpit_chat.dspy - Product-grade LLM conversation with context
# POST: action=send_message, iteration_id, message_text # POST: action=send_message, iteration_id, message_text, model_id, file_paths
# GET: action=list_messages, iteration_id (optional), task_id (optional) # GET: action=list_messages, iteration_id, task_id
import aiohttp
action = (params_kw or {}).get('action', 'list_messages') action = (params_kw or {}).get('action', 'list_messages')
dbname = get_module_dbname('pipeline-sdlc') dbname = get_module_dbname('pipeline-sdlc')
DEFAULT_SYSTEM_PROMPT = """你是一个专业的软件开发 Agent名为「开发产线驾驶舱」。你的职责是帮助用户完成软件开发生命周期的各个环节需求分析、设计、编码、测试、部署。
对话规则:
1. 简洁专业,用中文回复
2. 当用户描述需求时,先理解并复述确认,然后给出分析和建议
3. 如果用户提到了项目/迭代,主动关联上下文
4. 可以建议启动开发产线来推进工作
5. 对于代码相关问题,给出具体的代码示例
6. 记住对话历史,保持上下文连贯
当前你可以帮助用户完成:
- 创建和管理项目、迭代
- 数据表设计、CRUD 设计、API 设计
- 代码生成、规范检查、自动修复
- 测试用例生成、功能测试、Bug 管理
- 环境部署和验证"""
async def _load_agent_settings(sor, uid):
"""Load user's agent settings, return defaults if not set."""
recs = await sor.sqlExe(
"SELECT default_llm_id, system_prompt, temperature, max_context_messages FROM sd_agent_settings WHERE user_id=${uid}$",
{"uid": uid}
)
if recs:
r = recs[0]
return {
'llm_id': getattr(r, 'default_llm_id', None),
'system_prompt': getattr(r, 'system_prompt', None) or DEFAULT_SYSTEM_PROMPT,
'temperature': float(getattr(r, 'temperature', 0.7) or 0.7),
'max_context': int(getattr(r, 'max_context_messages', 30) or 30),
}
return {
'llm_id': None,
'system_prompt': DEFAULT_SYSTEM_PROMPT,
'temperature': 0.7,
'max_context': 30,
}
async def _select_model(sor, preferred_llm_id, has_files):
"""Select best model: prefer user choice, then multimodal if files, else first active text."""
# If user has preferred model, use it
if preferred_llm_id:
recs = await sor.sqlExe(
"SELECT id, name, provider, model_id, api_base, api_key, capabilities FROM llm WHERE id=${lid}$ AND status='active'",
{"lid": preferred_llm_id}
)
if recs:
return recs[0]
# Auto-select based on file presence
if has_files:
recs = await sor.sqlExe(
"SELECT id, name, provider, model_id, api_base, api_key, capabilities FROM llm WHERE status='active' AND capabilities LIKE '%multimodal%' LIMIT 1",
{}
)
if recs:
return recs[0]
# Fallback: first active model
recs = await sor.sqlExe(
"SELECT id, name, provider, model_id, api_base, api_key, capabilities FROM llm WHERE status='active' LIMIT 1",
{}
)
if recs:
return recs[0]
return None
async def _build_context(sor, iteration_id, task_id, max_msgs, system_prompt):
"""Build LLM messages array with Hermes-style context."""
messages = [{"role": "system", "content": system_prompt}]
# Project/iteration context
if iteration_id:
iters = await sor.sqlExe(
"SELECT i.iteration_name, i.iteration_type, i.status, i.scope, p.name as project_name, p.description, p.tech_stack "
"FROM sd_iterations i LEFT JOIN sd_projects p ON i.project_id=p.id WHERE i.id=${iid}$",
{"iid": iteration_id}
)
if iters:
it = iters[0]
ctx_parts = ["## 当前上下文"]
ctx_parts.append(f"项目: {getattr(it, 'project_name', '未知')}")
ctx_parts.append(f"迭代: {getattr(it, 'iteration_name', '未知')}")
ctx_parts.append(f"类型: {getattr(it, 'iteration_type', '')}")
ctx_parts.append(f"状态: {getattr(it, 'status', '')}")
desc = getattr(it, 'description', '')
if desc:
ctx_parts.append(f"项目描述: {desc[:500]}")
stack = getattr(it, 'tech_stack', '')
if stack:
ctx_parts.append(f"技术栈: {stack[:300]}")
scope = getattr(it, 'scope', '')
if scope:
ctx_parts.append(f"迭代范围: {scope[:500]}")
messages.append({"role": "system", "content": "\n".join(ctx_parts)})
# Task context
if task_id:
tasks = await sor.sqlExe(
"SELECT id, status, pipeline_id FROM pipeline_tasks WHERE id=${tid}$",
{"tid": task_id}
)
if tasks:
t = tasks[0]
messages.append({"role": "system", "content": f"关联 Pipeline 任务: {t.id}, 状态: {getattr(t, 'status', 'unknown')}"})
# Conversation history
where = []
params = {}
if task_id:
where.append("task_id=${tid}$")
params["tid"] = task_id
if iteration_id:
where.append("iteration_id=${iid}$")
params["iid"] = iteration_id
if where:
sql = f"SELECT role, content FROM sd_conversations WHERE {' OR '.join(where)} ORDER BY created_at DESC LIMIT ${max_msgs}$"
params["max_msgs"] = max_msgs
history = await sor.sqlExe(sql, params)
# Reverse to chronological order
for h in reversed(history):
role = getattr(h, 'role', 'user')
content = getattr(h, 'content', '')
if role in ('user', 'agent'):
messages.append({"role": "user" if role == "user" else "assistant", "content": content})
return messages
async def _call_llm(model_info, messages, temperature):
"""Call LLM API directly using model config from llm table."""
api_base = model_info.api_base.rstrip('/')
api_key = model_info.api_key
model_id = model_info.model_id
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": model_id,
"messages": messages,
"temperature": temperature,
}
timeout = aiohttp.ClientTimeout(total=120)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(f"{api_base}/chat/completions", headers=headers, json=payload) as resp:
if resp.status != 200:
text = await resp.text()
raise ValueError(f"LLM API error {resp.status}: {text[:300]}")
data = await resp.json()
content = data["choices"][0]["message"]["content"]
# Truncate overly long responses
if len(content) > 8000:
content = content[:8000] + "\n\n...(内容过长已截断)"
return content
# ==================== ACTION HANDLERS ====================
if action == 'send_message': if action == 'send_message':
iteration_id = (params_kw or {}).get('iteration_id', '') iteration_id = (params_kw or {}).get('iteration_id', '')
task_id = (params_kw or {}).get('task_id', '') task_id = (params_kw or {}).get('task_id', '')
message_text = (params_kw or {}).get('message_text', '').strip() message_text = (params_kw or {}).get('message_text', '').strip()
model_id = (params_kw or {}).get('model_id', '')
file_paths_raw = (params_kw or {}).get('file_paths', '[]')
if not iteration_id or not message_text: if not message_text:
return json.dumps({"error": "Missing iteration_id or message_text"}, ensure_ascii=False) return json.dumps({"error": "message_text is required"}, ensure_ascii=False)
# Parse file paths
has_files = False
try:
fps = json.loads(file_paths_raw)
has_files = len(fps) > 0
except Exception:
pass
uid = await get_user() uid = await get_user()
async with DBPools().sqlorContext(dbname) as sor: async with DBPools().sqlorContext(dbname) as sor:
# Save user message # 1. Save user message
msg_id = getID() msg_id = getID()
await sor.C('sd_conversations', { await sor.C('sd_conversations', {
'id': msg_id, 'id': msg_id,
'iteration_id': iteration_id, 'iteration_id': iteration_id or '',
'task_id': task_id or '', 'task_id': task_id or '',
'step_name': '', 'step_name': '',
'role': 'user', 'role': 'user',
'content': message_text, 'content': message_text,
'attachments': '[]', 'attachments': file_paths_raw,
'msg_type': 'text', 'msg_type': 'text',
'org_id': '0', 'org_id': '0',
'created_by': uid 'created_by': uid
}) })
# Generate agent response # 2. Load agent settings
# 1. Check if there's an active pipeline task for this iteration settings = await _load_agent_settings(sor, uid)
if task_id:
agent_reply = f"收到你的消息。当前任务 {task_id} 正在执行中,你的输入已记录。"
else:
# Check for existing iterations
iters = await sor.sqlExe(
"SELECT id, task_id, status FROM sd_iterations WHERE id=${iid}$", {"iid": iteration_id})
if iters:
it = iters[0]
if hasattr(it, 'task_id') and it.task_id:
agent_reply = f"收到。迭代任务 {it.task_id} 状态: {it.status}。"
else:
agent_reply = f"收到你的需求:「{message_text[:50]}...」。请确认是否要为此迭代启动开发产线?"
else:
agent_reply = f"收到你的需求:「{message_text[:50]}...」。建议先在项目管理中创建项目和迭代。"
# Save agent message # 3. Select model (preferred → auto by file type → first active)
selected_llm_id = model_id or settings['llm_id']
model_info = await _select_model(sor, selected_llm_id, has_files)
if not model_info:
return json.dumps({"error": "No active LLM model configured"}, ensure_ascii=False)
# 4. Build context
messages = await _build_context(
sor, iteration_id, task_id,
settings['max_context'], settings['system_prompt']
)
# Append current user message
messages.append({"role": "user", "content": message_text})
# 5. Call LLM
try:
agent_reply = await _call_llm(model_info, messages, settings['temperature'])
except Exception as e:
agent_reply = f"抱歉,模型调用失败: {str(e)[:200]}"
# 6. Save agent response
agent_msg_id = getID() agent_msg_id = getID()
await sor.C('sd_conversations', { await sor.C('sd_conversations', {
'id': agent_msg_id, 'id': agent_msg_id,
'iteration_id': iteration_id, 'iteration_id': iteration_id or '',
'task_id': task_id or '', 'task_id': task_id or '',
'step_name': '', 'step_name': '',
'role': 'agent', 'role': 'agent',
@ -63,10 +246,16 @@ if action == 'send_message':
'created_by': 'system' 'created_by': 'system'
}) })
return json.dumps({"success": True, "message_id": msg_id}, ensure_ascii=False) return json.dumps({
"success": True,
"message_id": msg_id,
"agent_reply": agent_reply,
"model_used": model_info.name,
}, ensure_ascii=False)
else: else:
# list_messages - return messages as Bricks widget JSON # list_messages - return conversation as Bricks widget JSON
iteration_id = (params_kw or {}).get('iteration_id', '') iteration_id = (params_kw or {}).get('iteration_id', '')
task_id = (params_kw or {}).get('task_id', '') task_id = (params_kw or {}).get('task_id', '')
@ -85,7 +274,6 @@ else:
sql = f"SELECT role, content, msg_type, created_at FROM sd_conversations WHERE {' OR '.join(where)} ORDER BY created_at ASC LIMIT 50" sql = f"SELECT role, content, msg_type, created_at FROM sd_conversations WHERE {' OR '.join(where)} ORDER BY created_at ASC LIMIT 50"
msgs = await sor.sqlExe(sql, params) msgs = await sor.sqlExe(sql, params)
# Build chat messages widget
msg_widgets = [] msg_widgets = []
for m in msgs: for m in msgs:
role = m.role if hasattr(m, 'role') else '' role = m.role if hasattr(m, 'role') else ''
@ -99,12 +287,12 @@ else:
elif role == 'user': elif role == 'user':
bg = '#dbeafe' bg = '#dbeafe'
align = 'flex-end' align = 'flex-end'
label = '' label = '\u4f60'
label_color = '#2563eb' label_color = '#2563eb'
else: else:
bg = '#f1f5f9' bg = '#f1f5f9'
align = 'center' align = 'center'
label = '系统' label = '\u7cfb\u7edf'
label_color = '#94a3b8' label_color = '#94a3b8'
msg_widgets.append({ msg_widgets.append({
@ -134,7 +322,7 @@ else:
msg_widgets.append({ msg_widgets.append({
"widgettype": "Text", "widgettype": "Text",
"options": { "options": {
"text": "暂无对话记录。选择一个迭代后,在下方输入框中开始对话。", "text": "\u6682\u65e0\u5bf9\u8bdd\u8bb0\u5f55\u3002\u9009\u62e9\u4e00\u4e2a\u8fed\u4ee3\u540e\uff0c\u5728\u4e0b\u65b9\u8f93\u5165\u6846\u4e2d\u5f00\u59cb\u5bf9\u8bdd\u3002",
"cfontsize": 0.9, "color": "#94a3b8", "padding": "20px" "cfontsize": 0.9, "color": "#94a3b8", "padding": "20px"
} }
}) })

View File

@ -0,0 +1,22 @@
# cockpit_model_options.dspy - Returns active models with capabilities for dropdown
# Used by cockpit model selector
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT id, name, provider, model_id, capabilities FROM llm WHERE status='active' ORDER BY name",
{}
)
rows = []
for r in recs:
rows.append({
'value': r.id,
'text': f"{r.name} ({r.provider})",
'provider': r.provider,
'model_id': r.model_id,
'capabilities': r.capabilities or 'text',
})
return rows

View File

@ -0,0 +1,10 @@
import json
result = json.dumps([{'value': '', 'text': '请选择'}], ensure_ascii=False)
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await sor.sqlExe("select id as value, name as text from pipelines order by name")
r = [{'value': '', 'text': '请选择'}] + list(rows)
return json.dumps(r, ensure_ascii=False)
except Exception as e:
debug(f'get_pipeline_options error: {e}')
return json.dumps(result, ensure_ascii=False)

View File

@ -16,12 +16,12 @@
"gap": "12px" "gap": "12px"
}, },
"subwidgets": [ "subwidgets": [
{ "widgettype": "Title2", "options": { "text": "开发产线驾驶舱" } }, { "widgettype": "Title2", "options": { "text": "\u5f00\u53d1\u4ea7\u7ebf\u9a7e\u9a76\u8231" } },
{ "widgettype": "Text", "options": { "text": "AI驱动的对话式开发", "cfontsize": 0.9, "color": "#94a3b8" } }, { "widgettype": "Text", "options": { "text": "AI\u9a71\u52a8\u7684\u5bf9\u8bdd\u5f0f\u5f00\u53d1", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Filler" }, { "widgettype": "Filler" },
{ {
"widgettype": "Button", "widgettype": "Button",
"options": { "name": "refresh", "label": "刷新", "css": "small" }, "options": { "name": "refresh", "label": "\u5237\u65b0", "css": "small" },
"binds": [{ "binds": [{
"wid": "self", "event": "click", "actiontype": "script", "target": "self", "wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": "location.reload();" "script": "location.reload();"
@ -76,8 +76,8 @@
"widgettype": "VBox", "widgettype": "VBox",
"options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" }, "options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" },
"subwidgets": [ "subwidgets": [
{ "widgettype": "Text", "options": { "text": "选择一个迭代后,在下方输入你的需求。", "cfontsize": 1, "color": "#64748b" } }, { "widgettype": "Text", "options": { "text": "\u9009\u62e9\u4e00\u4e2a\u8fed\u4ee3\u540e\uff0c\u5728\u4e0b\u65b9\u8f93\u5165\u4f60\u7684\u9700\u6c42\u3002", "cfontsize": 1, "color": "#64748b" } },
{ "widgettype": "Text", "options": { "text": "Agent 将自动分析需求、生成设计、驱动产线执行。", "cfontsize": 0.85, "color": "#94a3b8" } } { "widgettype": "Text", "options": { "text": "Agent \u5c06\u81ea\u52a8\u5206\u6790\u9700\u6c42\u3001\u751f\u6210\u8bbe\u8ba1\u3001\u9a71\u52a8\u4ea7\u7ebf\u6267\u884c\u3002", "cfontsize": 0.85, "color": "#94a3b8" } }
] ]
} }
] ]
@ -98,7 +98,7 @@
"event": "inputed", "event": "inputed",
"actiontype": "script", "actiontype": "script",
"target": "self", "target": "self",
"script": "var prompt=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:prompt,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));var at=new bricks.Text({text:'\u6b63\u5728\u5206\u6790\u9700\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);}var body='message_text='+encodeURIComponent(prompt)+'&iteration_id='+encodeURIComponent(iid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){location.reload();}else{if(at)at.set_text('\u9519\u8bef: '+(r.error||'\u672a\u77e5\u9519\u8bef'));}});" "script": "var p=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);var at=null;if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:p,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));at=new bricks.Text({text:'\\u6b63\\u5728\\u5206\\u6790\\u9700\\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);}var body='message_text='+encodeURIComponent(p)+'&iteration_id='+encodeURIComponent(iid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){if(at)at.set_text(r.agent_reply||'\\u5df2\\u5904\\u7406');var s=bricks.getWidgetById('stats_row',bricks.app);if(s)s.render({});}else{if(at)at.set_text('\\u9519\\u8bef: '+(r.error||'\\u672a\\u77e5\\u9519\\u8bef'));}});"
} }
] ]
} }

View File

@ -1 +0,0 @@
.

View File

@ -50,26 +50,26 @@
"severity": { "severity": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}", "dataurl": "{{entire_url('../api/get_search_severity.dspy')}}",
"valueField": "severity", "valueField": "value",
"textField": "severity_text" "textField": "text"
}, },
"priority": { "priority": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}", "dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "priority", "valueField": "value",
"textField": "priority_text" "textField": "text"
}, },
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
}, },
"reporter_type": { "reporter_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}",
"valueField": "reporter_type", "valueField": "value",
"textField": "reporter_type_text" "textField": "text"
} }
} }
}, },
@ -107,7 +107,7 @@
"valueField": "iteration_id", "valueField": "iteration_id",
"textField": "iteration_id_text", "textField": "iteration_id_text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "sd_iterations", "table": "sd_iterations",
"tblvalue": "id", "tblvalue": "id",
"tbltext": "iteration_name", "tbltext": "iteration_name",
@ -165,15 +165,15 @@
"default": "'major'", "default": "'major'",
"label": "严重程度", "label": "严重程度",
"uitype": "code", "uitype": "code",
"valueField": "severity", "valueField": "value",
"textField": "severity_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "severity", "valueField": "value",
"textField": "severity_text", "textField": "text",
"cond": "parentid='sd_bug_severity'" "cond": "parentid='sd_bug_severity'"
}, },
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}" "dataurl": "{{entire_url('../api/get_search_severity.dspy')}}"
@ -187,15 +187,15 @@
"default": "'P1'", "default": "'P1'",
"label": "优先级", "label": "优先级",
"uitype": "code", "uitype": "code",
"valueField": "priority", "valueField": "value",
"textField": "priority_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "priority", "valueField": "value",
"textField": "priority_text", "textField": "text",
"cond": "parentid='sd_priority'" "cond": "parentid='sd_priority'"
}, },
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}" "dataurl": "{{entire_url('../api/get_search_priority.dspy')}}"
@ -209,15 +209,15 @@
"default": "'open'", "default": "'open'",
"label": "Bug状态", "label": "Bug状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_bug_status'" "cond": "parentid='sd_bug_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}"
@ -230,15 +230,15 @@
"nullable": "no", "nullable": "no",
"label": "提交人类型", "label": "提交人类型",
"uitype": "code", "uitype": "code",
"valueField": "reporter_type", "valueField": "value",
"textField": "reporter_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "reporter_type", "valueField": "value",
"textField": "reporter_type_text", "textField": "text",
"cond": "parentid='sd_reporter_type'" "cond": "parentid='sd_reporter_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}"

View File

@ -16,12 +16,12 @@
"gap": "12px" "gap": "12px"
}, },
"subwidgets": [ "subwidgets": [
{ "widgettype": "Title2", "options": { "text": "开发产线驾驶舱" } }, { "widgettype": "Title2", "options": { "text": "\u5f00\u53d1\u4ea7\u7ebf\u9a7e\u9a76\u8231" } },
{ "widgettype": "Text", "options": { "text": "AI驱动的对话式开发", "cfontsize": 0.9, "color": "#94a3b8" } }, { "widgettype": "Text", "options": { "text": "AI\u9a71\u52a8\u7684\u5bf9\u8bdd\u5f0f\u5f00\u53d1", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Filler" }, { "widgettype": "Filler" },
{ {
"widgettype": "Button", "widgettype": "Button",
"options": { "name": "refresh", "label": "刷新", "css": "small" }, "options": { "name": "refresh", "label": "\u5237\u65b0", "css": "small" },
"binds": [{ "binds": [{
"wid": "self", "event": "click", "actiontype": "script", "target": "self", "wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": "location.reload();" "script": "location.reload();"
@ -76,8 +76,8 @@
"widgettype": "VBox", "widgettype": "VBox",
"options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" }, "options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" },
"subwidgets": [ "subwidgets": [
{ "widgettype": "Text", "options": { "text": "选择一个迭代后,在下方输入你的需求。", "cfontsize": 1, "color": "#64748b" } }, { "widgettype": "Text", "options": { "text": "\u9009\u62e9\u4e00\u4e2a\u8fed\u4ee3\u540e\uff0c\u5728\u4e0b\u65b9\u8f93\u5165\u4f60\u7684\u9700\u6c42\u3002", "cfontsize": 1, "color": "#64748b" } },
{ "widgettype": "Text", "options": { "text": "Agent 将自动分析需求、生成设计、驱动产线执行。", "cfontsize": 0.85, "color": "#94a3b8" } } { "widgettype": "Text", "options": { "text": "Agent \u5c06\u81ea\u52a8\u5206\u6790\u9700\u6c42\u3001\u751f\u6210\u8bbe\u8ba1\u3001\u9a71\u52a8\u4ea7\u7ebf\u6267\u884c\u3002", "cfontsize": 0.85, "color": "#94a3b8" } }
] ]
} }
] ]
@ -98,7 +98,7 @@
"event": "inputed", "event": "inputed",
"actiontype": "script", "actiontype": "script",
"target": "self", "target": "self",
"script": "var prompt=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:prompt,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));var at=new bricks.Text({text:'\u6b63\u5728\u5206\u6790\u9700\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);chat.scrollTo(0,chat.dom_element.scrollHeight);}var body='message_text='+encodeURIComponent(prompt)+'&iteration_id='+encodeURIComponent(iid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){location.reload();}else{if(at)at.set_text('\u9519\u8bef: '+(r.error||'\u672a\u77e5\u9519\u8bef'));}});" "script": "var p=params.prompt;var files=params.add_files||[];var iid='';try{var iter=bricks.getWidgetById('current_iteration_id',bricks.app);if(iter)iid=iter.options.value||'';}catch(e){}var chat=bricks.getWidgetById('chat_scroll',bricks.app);var at=null;if(chat){var ub=new bricks.HBox({width:'100%'});var um=new bricks.VBox({width:'85%',alignSelf:'flex-end',bgcolor:'#dbeafe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});um.add_widget(new bricks.Text({text:'\\u4f60',cfontsize:0.75,color:'#2563eb',fontWeight:'bold'}));um.add_widget(new bricks.Text({text:p,cfontsize:0.95,color:'#1e293b',whiteSpace:'pre-wrap'}));ub.add_widget(new bricks.VBox({css:'filler'}));ub.add_widget(um);ub.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/chat-user.svg')}));chat.add_widget(ub);var ab=new bricks.HBox({width:'100%'});var am=new bricks.VBox({width:'85%',alignSelf:'flex-start',bgcolor:'#e8f0fe',borderRadius:'12px',padding:'12px 16px',marginBottom:'10px',gap:'4px'});am.add_widget(new bricks.Text({text:'Agent',cfontsize:0.75,color:'#3b82f6',fontWeight:'bold'}));at=new bricks.Text({text:'\\u6b63\\u5728\\u5206\\u6790\\u9700\\u6c42...',cfontsize:0.85,color:'#64748b'});am.add_widget(at);ab.add_widget(new bricks.Svg({rate:2,url:bricks_resource('imgs/llm.svg')}));ab.add_widget(am);ab.add_widget(new bricks.VBox({css:'filler'}));chat.add_widget(ab);}var body='message_text='+encodeURIComponent(p)+'&iteration_id='+encodeURIComponent(iid)+'&action=send_message&file_paths='+encodeURIComponent(JSON.stringify(files.map(function(f){return f.name||f})));fetch('/pipeline-sdlc/api/cockpit_chat.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body}).then(function(r){return r.json()}).then(function(r){if(r.success){if(at)at.set_text(r.agent_reply||'\\u5df2\\u5904\\u7406');var s=bricks.getWidgetById('stats_row',bricks.app);if(s)s.render({});}else{if(at)at.set_text('\\u9519\\u8bef: '+(r.error||'\\u672a\\u77e5\\u9519\\u8bef'));}});"
} }
] ]
} }

View File

@ -0,0 +1,84 @@
"""
SDLC Dashboard Data API
Returns real-time stats for the dashboard widgets
"""
from sqlor.dbpools import DBPools
async def main():
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
# 1) Project stats
projects = await sor.sqlExe(
"SELECT COUNT(*) as total, SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active FROM sd_projects", {})
p = projects[0] if projects else None
# 2) Active iterations
iters = await sor.sqlExe(
"SELECT COUNT(*) as cnt FROM sd_iterations WHERE status='in_progress'", {})
active_iters = iters[0].cnt if iters else 0
# 3) Open bugs
bugs = await sor.sqlExe(
"SELECT COUNT(*) as cnt FROM sd_bugs WHERE status!='closed' AND status!='resolved'", {})
open_bugs = bugs[0].cnt if bugs else 0
# 4) Pending reviews
reviews = await sor.sqlExe(
"SELECT COUNT(*) as cnt FROM pipeline_human_tasks WHERE status='pending' AND task_type='review'", {})
pending_reviews = reviews[0].cnt if reviews else 0
# 5) Recent iterations with progress
recent_iters = await sor.sqlExe("""
SELECT i.name, i.status, i.start_date, i.end_date,
COALESCE(tc.total,0) as total_tasks,
COALESCE(tc.completed,0) as completed_tasks
FROM sd_iterations i
LEFT JOIN (
SELECT iteration_id, COUNT(*) as total,
SUM(CASE WHEN status='completed' THEN 1 ELSE 0 END) as completed
FROM sd_test_plans GROUP BY iteration_id
) tc ON tc.iteration_id = i.id
WHERE i.status != 'closed'
ORDER BY i.start_date DESC LIMIT 5
""", {})
iter_list = []
for r in recent_iters:
pct = round(r.completed_tasks / r.total_tasks * 100) if r.total_tasks > 0 else 0
iter_list.append({
'name': r.name,
'status': r.status,
'progress': pct,
'total': r.total_tasks,
'completed': r.completed_tasks
})
# 6) Bug by severity
bug_by_sev = await sor.sqlExe("""
SELECT COALESCE(severity,'unset') as severity, COUNT(*) as cnt
FROM sd_bugs WHERE status!='closed'
GROUP BY severity ORDER BY cnt DESC
""", {})
severity_map = {
'critical': {'label': '严重', 'color': '#FF4444'},
'major': {'label': '主要', 'color': '#FF8800'},
'minor': {'label': '次要', 'color': '#FFAA00'},
'trivial': {'label': '轻微', 'color': '#888888'},
'unset': {'label': '未分类', 'color': '#666666'}
}
bugs_data = []
for r in bug_by_sev:
sev = severity_map.get(r.severity, {'label': r.severity, 'color': '#888'})
bugs_data.append({'label': sev['label'], 'count': r.cnt, 'color': sev['color']})
return {
'projects_total': p.total if p else 0,
'projects_active': p.active if p else 0,
'active_iterations': active_iters,
'open_bugs': open_bugs,
'pending_reviews': pending_reviews,
'recent_iterations': iter_list,
'bugs_by_severity': bugs_data
}

View File

@ -1,53 +1,53 @@
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "20px"}, "options": {"width": "100%", "height": "100%", "padding": "24px", "gap": "24px"},
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "HBox",
"options": {"text": "📊 项目概览 Dashboard", "cfontsize": 24, "color": "#E0E0E0", "fontWeight": "bold"} "options": {"alignItems": "center", "gap": "12px"},
}, "subwidgets": [
{ {"widgettype": "Title2", "options": {"text": "开发产线仪表盘"}},
"widgettype": "Text", {"widgettype": "Text", "options": {"text": "项目/迭代/Bug实时概览", "cfontsize": 1.1, "color": "#888"}}
"options": {"text": "软件开发生命周期全局统计", "cfontsize": 14, "color": "#888888"} ]
}, },
{ {
"widgettype": "ResponsableBox", "widgettype": "ResponsableBox",
"options": {"gap": "16px", "minWidth": "180px"}, "options": {"gap": "16px", "minWidth": "200px"},
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cwidth": 25, "alignItems": "center"}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "8px", "border": "1px solid #334155", "cwidth": 25, "alignItems": "center"},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "12", "cfontsize": 36, "color": "#4A90D9", "fontWeight": "bold"}}, {"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#60a5fa", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "活跃项目", "cfontsize": 14, "color": "#E0E0E0"}}, {"widgettype": "Text", "options": {"text": "活跃项目", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Active Projects", "cfontsize": 11, "color": "#888888"}} {"widgettype": "Text", "options": {"text": "Active Projects", "cfontsize": 0.8, "color": "#94a3b8"}}
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cwidth": 25, "alignItems": "center"}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "8px", "border": "1px solid #334155", "cwidth": 25, "alignItems": "center"},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "5", "cfontsize": 36, "color": "#4AD97A", "fontWeight": "bold"}}, {"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#4ade80", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "进行中迭代", "cfontsize": 14, "color": "#E0E0E0"}}, {"widgettype": "Text", "options": {"text": "进行中迭代", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Active Iterations", "cfontsize": 11, "color": "#888888"}} {"widgettype": "Text", "options": {"text": "Active Sprints", "cfontsize": 0.8, "color": "#94a3b8"}}
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cwidth": 25, "alignItems": "center"}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "8px", "border": "1px solid #334155", "cwidth": 25, "alignItems": "center"},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "23", "cfontsize": 36, "color": "#D9A04A", "fontWeight": "bold"}}, {"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#fbbf24", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待处理Bug", "cfontsize": 14, "color": "#E0E0E0"}}, {"widgettype": "Text", "options": {"text": "待处理Bug", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Open Bugs", "cfontsize": 11, "color": "#888888"}} {"widgettype": "Text", "options": {"text": "Open Bugs", "cfontsize": 0.8, "color": "#94a3b8"}}
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cwidth": 25, "alignItems": "center"}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "8px", "border": "1px solid #334155", "cwidth": 25, "alignItems": "center"},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "3", "cfontsize": 36, "color": "#9A4AD9", "fontWeight": "bold"}}, {"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#a78bfa", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待审批", "cfontsize": 14, "color": "#E0E0E0"}}, {"widgettype": "Text", "options": {"text": "待审批", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Pending Reviews", "cfontsize": 11, "color": "#888888"}} {"widgettype": "Text", "options": {"text": "Pending Reviews", "cfontsize": 0.8, "color": "#94a3b8"}}
] ]
} }
] ]
@ -58,116 +58,18 @@
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "12px", "border": "1px solid #2A2A3E", "cwidth": 50}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "16px", "border": "1px solid #334155", "cwidth": 60},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "📈 迭代进度", "cfontsize": 16, "color": "#E0E0E0", "fontWeight": "bold"}}, {"widgettype": "Title3", "options": {"text": "迭代进度"}},
{ {"widgettype": "Text", "options": {"text": "暂无数据,请先创建项目和迭代", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
"widgettype": "VBox",
"options": {"gap": "8px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "Sprint 23 - API重构", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "72%", "cfontsize": 13, "color": "#4AD97A", "fontWeight": "bold"}}
]
},
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "Sprint 23b - 测试覆盖", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "45%", "cfontsize": 13, "color": "#D9A04A", "fontWeight": "bold"}}
]
},
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "Sprint 24 - 功能开发", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "规划中", "cfontsize": 13, "color": "#4A90D9", "fontWeight": "bold"}}
]
}
]
}
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "12px", "border": "1px solid #2A2A3E", "cwidth": 50}, "options": {"bgcolor": "#1e293b", "padding": "24px", "borderRadius": "12px", "gap": "16px", "border": "1px solid #334155", "cwidth": 40},
"subwidgets": [ "subwidgets": [
{"widgettype": "Text", "options": {"text": "🐛 Bug趋势", "cfontsize": 16, "color": "#E0E0E0", "fontWeight": "bold"}}, {"widgettype": "Title3", "options": {"text": "Bug 分布"}},
{ {"widgettype": "Text", "options": {"text": "暂无Bug数据", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
"widgettype": "VBox",
"options": {"gap": "8px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "严重 Critical", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "3", "cfontsize": 13, "color": "#D94A4A", "fontWeight": "bold"}}
]
},
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "高 High", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "8", "cfontsize": 13, "color": "#D9A04A", "fontWeight": "bold"}}
]
},
{
"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center", "justifyContent": "space-between"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "中 Medium", "cfontsize": 13, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "12", "cfontsize": 13, "color": "#4A90D9", "fontWeight": "bold"}}
]
}
]
}
]
}
]
},
{
"widgettype": "VBox",
"options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "12px", "border": "1px solid #2A2A3E"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "🚀 最近部署 Recent Deployments", "cfontsize": 16, "color": "#E0E0E0", "fontWeight": "bold"}},
{
"widgettype": "ResponsableBox",
"options": {"gap": "12px", "minWidth": "250px"},
"subwidgets": [
{
"widgettype": "VBox",
"options": {"backgroundColor": "#121212", "padding": "12px", "borderRadius": "6px", "gap": "4px", "cwidth": 33},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "✅ Production", "cfontsize": 13, "color": "#4AD97A", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "v2.2.5 → v2.2.6", "cfontsize": 12, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "2026-06-15 18:30", "cfontsize": 11, "color": "#888888"}}
]
},
{
"widgettype": "VBox",
"options": {"backgroundColor": "#121212", "padding": "12px", "borderRadius": "6px", "gap": "4px", "cwidth": 33},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "🔄 Staging", "cfontsize": 13, "color": "#D9A04A", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "v2.3.0-rc1", "cfontsize": 12, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "2026-06-16 10:00", "cfontsize": 11, "color": "#888888"}}
]
},
{
"widgettype": "VBox",
"options": {"backgroundColor": "#121212", "padding": "12px", "borderRadius": "6px", "gap": "4px", "cwidth": 33},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "🧪 Dev", "cfontsize": 13, "color": "#4A90D9", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "v2.3.0-dev.47", "cfontsize": 12, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "2026-06-16 14:22", "cfontsize": 11, "color": "#888888"}}
]
}
] ]
} }
] ]

View File

@ -54,14 +54,14 @@
"env_type": { "env_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}",
"valueField": "env_type", "valueField": "value",
"textField": "env_type_text" "textField": "text"
}, },
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
} }
} }
}, },
@ -97,7 +97,7 @@
"valueField": "project_id", "valueField": "project_id",
"textField": "project_id_text", "textField": "project_id_text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "sd_projects", "table": "sd_projects",
"tblvalue": "id", "tblvalue": "id",
"tbltext": "name", "tbltext": "name",
@ -114,15 +114,15 @@
"nullable": "no", "nullable": "no",
"label": "环境类型", "label": "环境类型",
"uitype": "code", "uitype": "code",
"valueField": "env_type", "valueField": "value",
"textField": "env_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "env_type", "valueField": "value",
"textField": "env_type_text", "textField": "text",
"cond": "parentid='sd_env_type'" "cond": "parentid='sd_env_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}"
@ -262,15 +262,15 @@
"default": "'configured'", "default": "'configured'",
"label": "环境状态", "label": "环境状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_env_status'" "cond": "parentid='sd_env_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}"

View File

@ -70,14 +70,14 @@
"iteration_type": { "iteration_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}",
"valueField": "iteration_type", "valueField": "value",
"textField": "iteration_type_text" "textField": "text"
}, },
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
} }
} }
}, },
@ -115,7 +115,7 @@
"valueField": "project_id", "valueField": "project_id",
"textField": "project_id_text", "textField": "project_id_text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "sd_projects", "table": "sd_projects",
"tblvalue": "id", "tblvalue": "id",
"tbltext": "name", "tbltext": "name",
@ -144,15 +144,15 @@
"default": "'new_feature'", "default": "'new_feature'",
"label": "迭代类型", "label": "迭代类型",
"uitype": "code", "uitype": "code",
"valueField": "iteration_type", "valueField": "value",
"textField": "iteration_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "iteration_type", "valueField": "value",
"textField": "iteration_type_text", "textField": "text",
"cond": "parentid='sd_iteration_type'" "cond": "parentid='sd_iteration_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}"
@ -185,15 +185,15 @@
"default": "'planning'", "default": "'planning'",
"label": "迭代状态", "label": "迭代状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_iteration_status'" "cond": "parentid='sd_iteration_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}"

View File

@ -23,6 +23,7 @@ if not userorgid:
} }
} }
ns['org_id'] = userorgid ns['org_id'] = userorgid
ns['created_by'] = userorgid
db = DBPools() db = DBPools()
dbname = get_module_dbname('pipeline_sdlc') dbname = get_module_dbname('pipeline_sdlc')

View File

@ -58,19 +58,26 @@
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
}, },
"project_type": { "project_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}",
"valueField": "project_type", "valueField": "value",
"textField": "project_type_text" "textField": "text"
} }
} }
}, },
,
"pipeline_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_pipeline_options.dspy')}}",
"valueField": "value",
"textField": "text"
}
"editexclouded":[ "editexclouded":[
"id", "id",
"created_at", "created_at",
@ -119,15 +126,15 @@
"default": "'web_app'", "default": "'web_app'",
"label": "项目类型", "label": "项目类型",
"uitype": "code", "uitype": "code",
"valueField": "project_type", "valueField": "value",
"textField": "project_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "project_type", "valueField": "value",
"textField": "project_type_text", "textField": "text",
"cond": "parentid='sd_project_type'" "cond": "parentid='sd_project_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}"
@ -170,15 +177,15 @@
"default": "'draft'", "default": "'draft'",
"label": "项目状态", "label": "项目状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_project_status'" "cond": "parentid='sd_project_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_status.dspy')}}"

View File

@ -18,6 +18,7 @@ if not userorgid:
} }
} }
ns['org_id'] = userorgid ns['org_id'] = userorgid
ns['created_by'] = userorgid

View File

@ -50,20 +50,20 @@
"case_type": { "case_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}",
"valueField": "case_type", "valueField": "value",
"textField": "case_type_text" "textField": "text"
}, },
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
}, },
"priority": { "priority": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}", "dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "priority", "valueField": "value",
"textField": "priority_text" "textField": "text"
} }
} }
}, },
@ -101,7 +101,7 @@
"valueField": "plan_id", "valueField": "plan_id",
"textField": "plan_id_text", "textField": "plan_id_text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "sd_test_plans", "table": "sd_test_plans",
"tblvalue": "id", "tblvalue": "id",
"tbltext": "plan_name", "tbltext": "plan_name",
@ -129,15 +129,15 @@
"nullable": "no", "nullable": "no",
"label": "用例类型", "label": "用例类型",
"uitype": "code", "uitype": "code",
"valueField": "case_type", "valueField": "value",
"textField": "case_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "case_type", "valueField": "value",
"textField": "case_type_text", "textField": "text",
"cond": "parentid='sd_test_case_type'" "cond": "parentid='sd_test_case_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}"
@ -151,15 +151,15 @@
"default": "'P2'", "default": "'P2'",
"label": "优先级", "label": "优先级",
"uitype": "code", "uitype": "code",
"valueField": "priority", "valueField": "value",
"textField": "priority_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "priority", "valueField": "value",
"textField": "priority_text", "textField": "text",
"cond": "parentid='sd_priority'" "cond": "parentid='sd_priority'"
}, },
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}" "dataurl": "{{entire_url('../api/get_search_priority.dspy')}}"
@ -209,15 +209,15 @@
"default": "'pending'", "default": "'pending'",
"label": "用例状态", "label": "用例状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_test_case_status'" "cond": "parentid='sd_test_case_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}"

View File

@ -61,14 +61,14 @@
"plan_type": { "plan_type": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}", "dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}",
"valueField": "plan_type", "valueField": "value",
"textField": "plan_type_text" "textField": "text"
}, },
"status": { "status": {
"uitype": "code", "uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}", "dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}",
"valueField": "status", "valueField": "value",
"textField": "status_text" "textField": "text"
} }
} }
}, },
@ -103,7 +103,7 @@
"valueField": "iteration_id", "valueField": "iteration_id",
"textField": "iteration_id_text", "textField": "iteration_id_text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "sd_iterations", "table": "sd_iterations",
"tblvalue": "id", "tblvalue": "id",
"tbltext": "iteration_name", "tbltext": "iteration_name",
@ -131,15 +131,15 @@
"nullable": "no", "nullable": "no",
"label": "方案类型", "label": "方案类型",
"uitype": "code", "uitype": "code",
"valueField": "plan_type", "valueField": "value",
"textField": "plan_type_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "plan_type", "valueField": "value",
"textField": "plan_type_text", "textField": "text",
"cond": "parentid='sd_test_plan_type'" "cond": "parentid='sd_test_plan_type'"
}, },
"dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}" "dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}"
@ -189,15 +189,15 @@
"default": "'draft'", "default": "'draft'",
"label": "方案状态", "label": "方案状态",
"uitype": "code", "uitype": "code",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"params": { "params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}", "dbname": "{{'sage'}}",
"table": "appcodes_kv", "table": "appcodes_kv",
"tblvalue": "k", "tblvalue": "k",
"tbltext": "v", "tbltext": "v",
"valueField": "status", "valueField": "value",
"textField": "status_text", "textField": "text",
"cond": "parentid='sd_test_plan_status'" "cond": "parentid='sd_test_plan_status'"
}, },
"dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}" "dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}"