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",
"title": "Bug管理",
"params": {
"sortby": ["created_at desc"],
"confidential_fields": [],
"browserfields": {
"alters": {
"iteration_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
"valueField": "iteration_id",
"textField": "iteration_id_text"
},
"severity": {
"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"
}
}
"tblname": "sd_bugs",
"title": "Bug管理",
"params": {
"sortby": [
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"alters": {
"iteration_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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')}}"
"severity": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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"}
]
"priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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}
}
]
}
}
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}",
"valueField": "value",
"textField": "text"
},
"reporter_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}",
"valueField": "value",
"textField": "text"
}
}
},
"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",
"title": "部署环境",
"params": {
"sortby": ["env_type"],
"confidential_fields": ["db_password", "ssh_key_path"],
"browserfields": {
"exclouded": ["db_password", "ssh_key_path"],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "project_id",
"textField": "project_id_text"
},
"env_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}",
"valueField": "env_type",
"textField": "env_type_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
}
}
"tblname": "sd_deploy_envs",
"title": "部署环境",
"params": {
"sortby": [
"env_type"
],
"confidential_fields": [
"db_password",
"ssh_key_path"
],
"browserfields": {
"exclouded": [
"db_password",
"ssh_key_path"
],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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')}}"
"env_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"record_toolbar": [
{
"label": "验证连接",
"actiontype": "dspy",
"url": "{{entire_url('../api/env_verify.dspy')}}",
"options": {"icon": "check", "cwidth": 16, "cheight": 9}
}
]
}
}
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_env_status.dspy')}}",
"valueField": "value",
"textField": "text"
}
}
},
"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",
"title": "迭代管理",
"params": {
"sortby": ["created_at desc"],
"confidential_fields": [],
"browserfields": {
"exclouded": ["scope"],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "project_id",
"textField": "project_id_text"
},
"iteration_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}",
"valueField": "iteration_type",
"textField": "iteration_type_text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
}
}
"tblname": "sd_iterations",
"title": "迭代管理",
"params": {
"sortby": [
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"exclouded": [
"scope"
],
"alters": {
"project_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_project_options.dspy')}}",
"valueField": "value",
"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')}}"
"iteration_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"data_filter": {
"AND": [
{"field": "project_id", "op": "=", "var": "project_input"},
{"field": "iteration_name", "op": "LIKE", "var": "name_input"},
{"field": "status", "op": "=", "var": "status_input"}
]
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_iteration_status.dspy')}}",
"valueField": "value",
"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_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"
}
]
}
}
{
"field": "iteration_name",
"op": "LIKE",
"var": "name_input"
},
{
"field": "status",
"op": "=",
"var": "status_input"
}
]
},
"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",
"title": "项目管理",
"params": {
"sortby": ["created_at desc"],
"logined_userorgid": "org_id",
"confidential_fields": [],
"browserfields": {
"exclouded": ["tech_stack"],
"alters": {
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
},
"project_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}",
"valueField": "project_type",
"textField": "project_type_text"
}
}
"tblname": "sd_projects",
"title": "项目管理",
"params": {
"sortby": [
"created_at desc"
],
"logined_userorgid": "org_id",
"confidential_fields": [],
"browserfields": {
"exclouded": [
"tech_stack"
],
"alters": {
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_status.dspy')}}",
"valueField": "value",
"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')}}"
"project_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_project_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"data_filter": {
"AND": [
{"field": "name", "op": "LIKE", "var": "name_input"},
{"field": "status", "op": "=", "var": "status_input"},
{"field": "project_type", "op": "=", "var": "type_input"}
]
"pipeline_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_pipeline_options.dspy')}}",
"valueField": "value",
"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": "project_id",
"title": "迭代列表",
"url": "{{entire_url('../sd_iteration_list?project_id=${id}')}}",
"subtable": "sd_iterations"
}
]
}
}
{
"field": "status",
"op": "=",
"var": "status_input"
},
{
"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",
"title": "测试用例",
"params": {
"sortby": ["priority desc", "created_at desc"],
"confidential_fields": [],
"browserfields": {
"alters": {
"plan_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_test_plan_options.dspy')}}",
"valueField": "plan_id",
"textField": "plan_id_text"
},
"case_type": {
"uitype": "code",
"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"
}
}
"tblname": "sd_test_cases",
"title": "测试用例",
"params": {
"sortby": [
"priority desc",
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"alters": {
"plan_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_test_plan_options.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')}}"
"case_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_case_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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"}
]
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_case_status.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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",
"title": "测试方案",
"params": {
"sortby": ["created_at desc"],
"confidential_fields": [],
"browserfields": {
"alters": {
"iteration_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
"valueField": "iteration_id",
"textField": "iteration_id_text"
},
"plan_type": {
"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"
}
}
"tblname": "sd_test_plans",
"title": "测试方案",
"params": {
"sortby": [
"created_at desc"
],
"confidential_fields": [],
"browserfields": {
"alters": {
"iteration_id": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_iteration_options.dspy')}}",
"valueField": "value",
"textField": "text"
},
"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')}}"
"plan_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_type.dspy')}}",
"valueField": "value",
"textField": "text"
},
"subtables": [
{
"field": "plan_id",
"title": "测试用例",
"url": "{{entire_url('../sd_test_case_list?plan_id=${id}')}}",
"subtable": "sd_test_cases"
}
]
}
}
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_plan_status.dspy')}}",
"valueField": "value",
"textField": "text"
}
}
},
"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
"""
RBAC path registration for pipeline-sdlc module.
Run: cd ~/repos/sage && ./py3/bin/python ~/repos/pipeline-sdlc/scripts/load_path.py
"""
import os
import sys
import subprocess
"""RBAC path registration for pipeline-sdlc module."""
import os, sys, subprocess
MOD = "pipeline_sdlc"
MOD = "pipeline-sdlc"
PATHS_ANY = [
f"/{MOD}/index.ui",
f"/{MOD}/styles/sdlc.css",
]
PATHS_LOGINED = [
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
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():
"""Find Sage root directory."""
for candidate in [
os.path.expanduser("~/repos/sage"),
os.path.expanduser("~/sage"),
]:
if os.path.isdir(os.path.join(candidate, "wwwroot")):
return candidate
def find_app_root():
for c in [os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
os.path.expanduser("~/pipeline")]:
if os.path.isfile(os.path.join(c, "set_role_perm.py")):
return c
return None
def main():
sage_root = find_sage_root()
if not sage_root:
print("ERROR: Sage root not found")
root = find_app_root()
if not root:
print("ERROR: pipeline root not found")
sys.exit(1)
set_perm = os.path.join(sage_root, "scripts", "set_role_perm.py")
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
set_perm = os.path.join(root, "set_role_perm.py")
py = sys.executable
count = 0
for role, paths in [("any", PATHS_ANY), ("logined", PATHS_LOGINED)]:
for path in paths:
cmd = [python, set_perm, role, path]
result = subprocess.run(cmd, capture_output=True, text=True, cwd=sage_root)
if result.returncode == 0:
cmd = [py, set_perm, role, path]
r = subprocess.run(cmd, capture_output=True, text=True, cwd=root)
if r.returncode == 0:
count += 1
else:
print(f"WARN: {path} ({role}): {result.stderr.strip()}")
print(f"Registered {count} paths for module '{MOD}'")
print(f" WARN: {path} -> {r.stderr.strip()[-80:]}")
print(f"Registered {count}/{len(PATHS_ANY)+len(PATHS_LOGINED)} paths for {MOD}")
if __name__ == "__main__":

View File

@ -1,58 +1,241 @@
# cockpit_chat.dspy - Send message or list conversation
# POST: action=send_message, iteration_id, message_text
# GET: action=list_messages, iteration_id (optional), task_id (optional)
# cockpit_chat.dspy - Product-grade LLM conversation with context
# POST: action=send_message, iteration_id, message_text, model_id, file_paths
# GET: action=list_messages, iteration_id, task_id
import aiohttp
action = (params_kw or {}).get('action', 'list_messages')
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':
iteration_id = (params_kw or {}).get('iteration_id', '')
task_id = (params_kw or {}).get('task_id', '')
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:
return json.dumps({"error": "Missing iteration_id or message_text"}, ensure_ascii=False)
if not message_text:
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()
async with DBPools().sqlorContext(dbname) as sor:
# Save user message
# 1. Save user message
msg_id = getID()
await sor.C('sd_conversations', {
'id': msg_id,
'iteration_id': iteration_id,
'iteration_id': iteration_id or '',
'task_id': task_id or '',
'step_name': '',
'role': 'user',
'content': message_text,
'attachments': '[]',
'attachments': file_paths_raw,
'msg_type': 'text',
'org_id': '0',
'created_by': uid
})
# Generate agent response
# 1. Check if there's an active pipeline task for this iteration
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]}...」。建议先在项目管理中创建项目和迭代。"
# 2. Load agent settings
settings = await _load_agent_settings(sor, uid)
# 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()
await sor.C('sd_conversations', {
'id': agent_msg_id,
'iteration_id': iteration_id,
'iteration_id': iteration_id or '',
'task_id': task_id or '',
'step_name': '',
'role': 'agent',
@ -63,10 +246,16 @@ if action == 'send_message':
'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:
# list_messages - return messages as Bricks widget JSON
# list_messages - return conversation as Bricks widget JSON
iteration_id = (params_kw or {}).get('iteration_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"
msgs = await sor.sqlExe(sql, params)
# Build chat messages widget
msg_widgets = []
for m in msgs:
role = m.role if hasattr(m, 'role') else ''
@ -99,12 +287,12 @@ else:
elif role == 'user':
bg = '#dbeafe'
align = 'flex-end'
label = ''
label = '\u4f60'
label_color = '#2563eb'
else:
bg = '#f1f5f9'
align = 'center'
label = '系统'
label = '\u7cfb\u7edf'
label_color = '#94a3b8'
msg_widgets.append({
@ -134,7 +322,7 @@ else:
msg_widgets.append({
"widgettype": "Text",
"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"
}
})

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"
},
"subwidgets": [
{ "widgettype": "Title2", "options": { "text": "开发产线驾驶舱" } },
{ "widgettype": "Text", "options": { "text": "AI驱动的对话式开发", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Title2", "options": { "text": "\u5f00\u53d1\u4ea7\u7ebf\u9a7e\u9a76\u8231" } },
{ "widgettype": "Text", "options": { "text": "AI\u9a71\u52a8\u7684\u5bf9\u8bdd\u5f0f\u5f00\u53d1", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Filler" },
{
"widgettype": "Button",
"options": { "name": "refresh", "label": "刷新", "css": "small" },
"options": { "name": "refresh", "label": "\u5237\u65b0", "css": "small" },
"binds": [{
"wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": "location.reload();"
@ -76,8 +76,8 @@
"widgettype": "VBox",
"options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" },
"subwidgets": [
{ "widgettype": "Text", "options": { "text": "选择一个迭代后,在下方输入你的需求。", "cfontsize": 1, "color": "#64748b" } },
{ "widgettype": "Text", "options": { "text": "Agent 将自动分析需求、生成设计、驱动产线执行。", "cfontsize": 0.85, "color": "#94a3b8" } }
{ "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 \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",
"actiontype": "script",
"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": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}",
"valueField": "severity",
"textField": "severity_text"
"valueField": "value",
"textField": "text"
},
"priority": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}",
"valueField": "priority",
"textField": "priority_text"
"valueField": "value",
"textField": "text"
},
"status": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}",
"valueField": "status",
"textField": "status_text"
"valueField": "value",
"textField": "text"
},
"reporter_type": {
"uitype": "code",
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}",
"valueField": "reporter_type",
"textField": "reporter_type_text"
"valueField": "value",
"textField": "text"
}
}
},
@ -107,7 +107,7 @@
"valueField": "iteration_id",
"textField": "iteration_id_text",
"params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}",
"dbname": "{{'sage'}}",
"table": "sd_iterations",
"tblvalue": "id",
"tbltext": "iteration_name",
@ -165,15 +165,15 @@
"default": "'major'",
"label": "严重程度",
"uitype": "code",
"valueField": "severity",
"textField": "severity_text",
"valueField": "value",
"textField": "text",
"params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}",
"dbname": "{{'sage'}}",
"table": "appcodes_kv",
"tblvalue": "k",
"tbltext": "v",
"valueField": "severity",
"textField": "severity_text",
"valueField": "value",
"textField": "text",
"cond": "parentid='sd_bug_severity'"
},
"dataurl": "{{entire_url('../api/get_search_severity.dspy')}}"
@ -187,15 +187,15 @@
"default": "'P1'",
"label": "优先级",
"uitype": "code",
"valueField": "priority",
"textField": "priority_text",
"valueField": "value",
"textField": "text",
"params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}",
"dbname": "{{'sage'}}",
"table": "appcodes_kv",
"tblvalue": "k",
"tbltext": "v",
"valueField": "priority",
"textField": "priority_text",
"valueField": "value",
"textField": "text",
"cond": "parentid='sd_priority'"
},
"dataurl": "{{entire_url('../api/get_search_priority.dspy')}}"
@ -209,15 +209,15 @@
"default": "'open'",
"label": "Bug状态",
"uitype": "code",
"valueField": "status",
"textField": "status_text",
"valueField": "value",
"textField": "text",
"params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}",
"dbname": "{{'sage'}}",
"table": "appcodes_kv",
"tblvalue": "k",
"tbltext": "v",
"valueField": "status",
"textField": "status_text",
"valueField": "value",
"textField": "text",
"cond": "parentid='sd_bug_status'"
},
"dataurl": "{{entire_url('../api/get_search_bug_status.dspy')}}"
@ -230,15 +230,15 @@
"nullable": "no",
"label": "提交人类型",
"uitype": "code",
"valueField": "reporter_type",
"textField": "reporter_type_text",
"valueField": "value",
"textField": "text",
"params": {
"dbname": "{{get_module_dbname('pipeline_sdlc')}}",
"dbname": "{{'sage'}}",
"table": "appcodes_kv",
"tblvalue": "k",
"tbltext": "v",
"valueField": "reporter_type",
"textField": "reporter_type_text",
"valueField": "value",
"textField": "text",
"cond": "parentid='sd_reporter_type'"
},
"dataurl": "{{entire_url('../api/get_search_reporter_type.dspy')}}"

View File

@ -16,12 +16,12 @@
"gap": "12px"
},
"subwidgets": [
{ "widgettype": "Title2", "options": { "text": "开发产线驾驶舱" } },
{ "widgettype": "Text", "options": { "text": "AI驱动的对话式开发", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Title2", "options": { "text": "\u5f00\u53d1\u4ea7\u7ebf\u9a7e\u9a76\u8231" } },
{ "widgettype": "Text", "options": { "text": "AI\u9a71\u52a8\u7684\u5bf9\u8bdd\u5f0f\u5f00\u53d1", "cfontsize": 0.9, "color": "#94a3b8" } },
{ "widgettype": "Filler" },
{
"widgettype": "Button",
"options": { "name": "refresh", "label": "刷新", "css": "small" },
"options": { "name": "refresh", "label": "\u5237\u65b0", "css": "small" },
"binds": [{
"wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": "location.reload();"
@ -76,8 +76,8 @@
"widgettype": "VBox",
"options": { "alignItems": "center", "padding": "40px 0", "gap": "12px" },
"subwidgets": [
{ "widgettype": "Text", "options": { "text": "选择一个迭代后,在下方输入你的需求。", "cfontsize": 1, "color": "#64748b" } },
{ "widgettype": "Text", "options": { "text": "Agent 将自动分析需求、生成设计、驱动产线执行。", "cfontsize": 0.85, "color": "#94a3b8" } }
{ "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 \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",
"actiontype": "script",
"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",
"options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "20px"},
"options": {"width": "100%", "height": "100%", "padding": "24px", "gap": "24px"},
"subwidgets": [
{
"widgettype": "Text",
"options": {"text": "📊 项目概览 Dashboard", "cfontsize": 24, "color": "#E0E0E0", "fontWeight": "bold"}
},
{
"widgettype": "Text",
"options": {"text": "软件开发生命周期全局统计", "cfontsize": 14, "color": "#888888"}
"widgettype": "HBox",
"options": {"alignItems": "center", "gap": "12px"},
"subwidgets": [
{"widgettype": "Title2", "options": {"text": "开发产线仪表盘"}},
{"widgettype": "Text", "options": {"text": "项目/迭代/Bug实时概览", "cfontsize": 1.1, "color": "#888"}}
]
},
{
"widgettype": "ResponsableBox",
"options": {"gap": "16px", "minWidth": "180px"},
"options": {"gap": "16px", "minWidth": "200px"},
"subwidgets": [
{
"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": [
{"widgettype": "Text", "options": {"text": "12", "cfontsize": 36, "color": "#4A90D9", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "活跃项目", "cfontsize": 14, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "Active Projects", "cfontsize": 11, "color": "#888888"}}
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#60a5fa", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "活跃项目", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Active Projects", "cfontsize": 0.8, "color": "#94a3b8"}}
]
},
{
"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": [
{"widgettype": "Text", "options": {"text": "5", "cfontsize": 36, "color": "#4AD97A", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "进行中迭代", "cfontsize": 14, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "Active Iterations", "cfontsize": 11, "color": "#888888"}}
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#4ade80", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "进行中迭代", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Active Sprints", "cfontsize": 0.8, "color": "#94a3b8"}}
]
},
{
"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": [
{"widgettype": "Text", "options": {"text": "23", "cfontsize": 36, "color": "#D9A04A", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待处理Bug", "cfontsize": 14, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "Open Bugs", "cfontsize": 11, "color": "#888888"}}
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#fbbf24", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待处理Bug", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Open Bugs", "cfontsize": 0.8, "color": "#94a3b8"}}
]
},
{
"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": [
{"widgettype": "Text", "options": {"text": "3", "cfontsize": 36, "color": "#9A4AD9", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待审批", "cfontsize": 14, "color": "#E0E0E0"}},
{"widgettype": "Text", "options": {"text": "Pending Reviews", "cfontsize": 11, "color": "#888888"}}
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2.5, "color": "#a78bfa", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"text": "待审批", "cfontsize": 1, "color": "#e2e8f0"}},
{"widgettype": "Text", "options": {"text": "Pending Reviews", "cfontsize": 0.8, "color": "#94a3b8"}}
]
}
]
@ -58,116 +58,18 @@
"subwidgets": [
{
"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": [
{"widgettype": "Text", "options": {"text": "📈 迭代进度", "cfontsize": 16, "color": "#E0E0E0", "fontWeight": "bold"}},
{
"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": "Title3", "options": {"text": "迭代进度"}},
{"widgettype": "Text", "options": {"text": "暂无数据,请先创建项目和迭代", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
]
},
{
"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": [
{"widgettype": "Text", "options": {"text": "🐛 Bug趋势", "cfontsize": 16, "color": "#E0E0E0", "fontWeight": "bold"}},
{
"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"}}
]
}
{"widgettype": "Title3", "options": {"text": "Bug 分布"}},
{"widgettype": "Text", "options": {"text": "暂无Bug数据", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
]
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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