fix: 当前项目/迭代按钮改为选择器弹窗,无选中时显示列表供选择

- 当前项目按钮:无项目时弹出选择器,有项目时显示详情
- 当前迭代按钮:无迭代时弹出选择器(按项目过滤),有迭代时显示详情
- 新增 cockpit_project_picker.dspy:返回项目列表
- 新增 cockpit_iteration_picker.dspy:返回迭代列表(支持project_id过滤)
- 新增 cockpit_context_update.dspy:保存选择到 pipeline_agent_settings
This commit is contained in:
yumoqing 2026-08-07 15:06:17 +08:00
parent 3f91752d06
commit 82f9fb6764
5 changed files with 124 additions and 4 deletions

View File

@ -0,0 +1,72 @@
# cockpit_context_update.dspy - Save user's selected project/iteration to pipeline_agent_settings
# POST params: project_id, iteration_id (at least one required)
# Also looks up names for the newly set values
import json
uid = await get_user()
pid = (params_kw or {}).get('project_id', '')
iid = (params_kw or {}).get('iteration_id', '')
if not pid and not iid:
return json.dumps({"success": False, "error": "请提供 project_id 或 iteration_id"}, ensure_ascii=False)
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
# Upsert agent_settings
existing = await sor.sqlExe(
"SELECT id, current_project_id, current_iteration_id FROM pipeline_agent_settings WHERE user_id=${uid}$",
{"uid": uid}
)
if existing:
cur_pid = getattr(existing[0], 'current_project_id', '') or ''
cur_iid = getattr(existing[0], 'current_iteration_id', '') or ''
new_pid = pid if pid else cur_pid
new_iid = iid if iid else ('' if pid else cur_iid)
# If project changed but iteration not specified, clear iteration
if pid and pid != cur_pid and not iid:
new_iid = ''
await sor.sqlExe(
"UPDATE pipeline_agent_settings SET current_project_id=${pid}$, current_iteration_id=${iid}$ WHERE user_id=${uid}$",
{"pid": new_pid, "iid": new_iid, "uid": uid}
)
else:
new_pid = pid
new_iid = iid
await sor.C('pipeline_agent_settings', {
'id': getID(),
'user_id': uid,
'current_project_id': new_pid,
'current_iteration_id': new_iid,
})
# Look up names
pname = ''
iname = ''
ws_dir = ''
if new_pid:
precs = await sor.sqlExe(
"SELECT name, workspace_dir FROM sd_projects WHERE id=${pid}$",
{"pid": new_pid}
)
if precs and len(precs) > 0:
pname = getattr(precs[0], 'name', '') or ''
ws_dir = getattr(precs[0], 'workspace_dir', '') or ''
if new_iid:
irecs = await sor.sqlExe(
"SELECT iteration_name FROM sd_iterations WHERE id=${iid}$",
{"iid": new_iid}
)
if irecs and len(irecs) > 0:
iname = getattr(irecs[0], 'iteration_name', '') or ''
return json.dumps({
"success": True,
"project_id": new_pid,
"iteration_id": new_iid,
"project_name": pname,
"iteration_name": iname,
"workspace_dir": ws_dir,
}, ensure_ascii=False)

View File

@ -0,0 +1,29 @@
# cockpit_iteration_picker.dspy - Returns iterations as {value, text} for dropdown
# Optional query param: project_id - filter iterations by project
# Used by cockpit context bar iteration picker
import json
pid = (params_kw or {}).get('project_id', '')
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
if pid:
recs = await sor.sqlExe(
"SELECT id, iteration_name FROM sd_iterations WHERE project_id=${pid}$ ORDER BY iteration_name",
{"pid": pid}
)
else:
recs = await sor.sqlExe(
"SELECT id, iteration_name FROM sd_iterations ORDER BY iteration_name",
{}
)
rows = []
for r in recs:
rows.append({
'value': r.id,
'text': r.iteration_name,
})
return rows

View File

@ -0,0 +1,19 @@
# cockpit_project_picker.dspy - Returns all projects as {value, text} for dropdown
# Used by cockpit context bar project picker
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT id, name FROM sd_projects ORDER BY name",
{}
)
rows = []
for r in recs:
rows.append({
'value': r.id,
'text': r.name,
})
return rows

View File

@ -61,7 +61,7 @@
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var pid=d.project_id||'';if(!pid){bricks.show_error({title:'提示', message:'请选择项目'});return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'项目名称:'+(d.project_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'项目ID'+pid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'工作目录:'+(d.workspace_dir||'/d/pipeline/workspaces/'+pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'项目详情',cwidth:36,cheight:16,auto_open:true});pw.content_w.add_widget(box);"
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var pid=d.project_id||'';if(!pid){var pw=new bricks.PopupWindow({title:'选择项目',cwidth:32,cheight:16,auto_open:true});var form=new bricks.Form({name:'project_picker',cols:1,fields:[{name:'project_id',uitype:'code',label:'项目',placeholder:'请选择项目',cwidth:26,dataurl:'/pipeline-sdlc/api/cockpit_project_picker.dspy',valueField:'value',textField:'text'}]});var vb=new bricks.VBox({padding:'12px',gap:'12px'});vb.add_widget(new bricks.Text({text:'没有当前项目,请选择一个项目:',cfontsize:0.95,color:'#64748b'}));vb.add_widget(form);var btn=new bricks.Button({label:'确定',css:'primary'});btn.bind('click',function(){var v=form.getValue();var spid=(v||{}).project_id||'';if(!spid){bricks.show_error({title:'提示',message:'请选择一个项目'});return;}fetch('/pipeline-sdlc/api/cockpit_context_update.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'project_id='+encodeURIComponent(spid)}).then(function(r){return r.json()}).then(function(r){if(r.success){pw.destroy();var cd=bricks.getWidgetById('context_data',bricks.app);if(cd)cd.render({});}else{bricks.show_error({title:'失败',message:r.error||'保存失败'});}});});vb.add_widget(btn);pw.content_w.add_widget(vb);return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'项目名称:'+(d.project_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'项目ID'+pid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'工作目录:'+(d.workspace_dir||'/d/pipeline/workspaces/'+pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'项目详情',cwidth:36,cheight:16,auto_open:true});pw.content_w.add_widget(box);"
}
]
},
@ -79,7 +79,7 @@
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var iid=d.iteration_id||'';var pid=d.project_id||'';if(!iid){bricks.show_error({title:'提示', message:'请选择迭代'});return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'迭代名称:'+(d.iteration_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'迭代ID'+iid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'所属项目:'+(d.project_name||pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'迭代详情',cwidth:36,cheight:14,auto_open:true});pw.content_w.add_widget(box);"
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var iid=d.iteration_id||'';var pid=d.project_id||'';if(!iid){var iter_url='/pipeline-sdlc/api/cockpit_iteration_picker.dspy';if(pid)iter_url+='?project_id='+encodeURIComponent(pid);var pw=new bricks.PopupWindow({title:'选择迭代',cwidth:32,cheight:16,auto_open:true});var form=new bricks.Form({name:'iteration_picker',cols:1,fields:[{name:'iteration_id',uitype:'code',label:'迭代',placeholder:'请选择迭代',cwidth:26,dataurl:iter_url,valueField:'value',textField:'text'}]});var vb=new bricks.VBox({padding:'12px',gap:'12px'});vb.add_widget(new bricks.Text({text:'没有当前迭代,请选择一个迭代:',cfontsize:0.95,color:'#64748b'}));vb.add_widget(form);var btn=new bricks.Button({label:'确定',css:'primary'});btn.bind('click',function(){var v=form.getValue();var siid=(v||{}).iteration_id||'';if(!siid){bricks.show_error({title:'提示',message:'请选择一个迭代'});return;}var body='iteration_id='+encodeURIComponent(siid);if(pid)body+='&project_id='+encodeURIComponent(pid);fetch('/pipeline-sdlc/api/cockpit_context_update.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){pw.destroy();var cd=bricks.getWidgetById('context_data',bricks.app);if(cd)cd.render({});}else{bricks.show_error({title:'失败',message:r.error||'保存失败'});}});});vb.add_widget(btn);pw.content_w.add_widget(vb);return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'迭代名称:'+(d.iteration_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'迭代ID'+iid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'所属项目:'+(d.project_name||pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'迭代详情',cwidth:36,cheight:14,auto_open:true});pw.content_w.add_widget(box);"
}
]
},

View File

@ -80,7 +80,7 @@
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var pid=d.project_id||'';if(!pid){bricks.show_error({title:'提示', message:'请选择项目'});return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'项目名称:'+(d.project_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'项目ID'+pid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'工作目录:'+(d.workspace_dir||'/d/pipeline/workspaces/'+pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'项目详情',cwidth:36,cheight:16,auto_open:true});pw.content_w.add_widget(box);"
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var pid=d.project_id||'';if(!pid){var pw=new bricks.PopupWindow({title:'选择项目',cwidth:32,cheight:16,auto_open:true});var form=new bricks.Form({name:'project_picker',cols:1,fields:[{name:'project_id',uitype:'code',label:'项目',placeholder:'请选择项目',cwidth:26,dataurl:'/pipeline-sdlc/api/cockpit_project_picker.dspy',valueField:'value',textField:'text'}]});var vb=new bricks.VBox({padding:'12px',gap:'12px'});vb.add_widget(new bricks.Text({text:'没有当前项目,请选择一个项目:',cfontsize:0.95,color:'#64748b'}));vb.add_widget(form);var btn=new bricks.Button({label:'确定',css:'primary'});btn.bind('click',function(){var v=form.getValue();var spid=(v||{}).project_id||'';if(!spid){bricks.show_error({title:'提示',message:'请选择一个项目'});return;}fetch('/pipeline-sdlc/api/cockpit_context_update.dspy',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'project_id='+encodeURIComponent(spid)}).then(function(r){return r.json()}).then(function(r){if(r.success){pw.destroy();var cd=bricks.getWidgetById('context_data',bricks.app);if(cd)cd.render({});}else{bricks.show_error({title:'失败',message:r.error||'保存失败'});}});});vb.add_widget(btn);pw.content_w.add_widget(vb);return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'项目名称:'+(d.project_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'项目ID'+pid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'工作目录:'+(d.workspace_dir||'/d/pipeline/workspaces/'+pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'项目详情',cwidth:36,cheight:16,auto_open:true});pw.content_w.add_widget(box);"
}
]
},
@ -98,7 +98,7 @@
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var iid=d.iteration_id||'';var pid=d.project_id||'';if(!iid){bricks.show_error({title:'提示', message:'请选择迭代'});return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'迭代名称:'+(d.iteration_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'迭代ID'+iid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'所属项目:'+(d.project_name||pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'迭代详情',cwidth:36,cheight:14,auto_open:true});pw.content_w.add_widget(box);"
"script": "var ctx=bricks.getWidgetById('context_data',bricks.app);var d=ctx?ctx.getValue():{};var iid=d.iteration_id||'';var pid=d.project_id||'';if(!iid){var iter_url='/pipeline-sdlc/api/cockpit_iteration_picker.dspy';if(pid)iter_url+='?project_id='+encodeURIComponent(pid);var pw=new bricks.PopupWindow({title:'选择迭代',cwidth:32,cheight:16,auto_open:true});var form=new bricks.Form({name:'iteration_picker',cols:1,fields:[{name:'iteration_id',uitype:'code',label:'迭代',placeholder:'请选择迭代',cwidth:26,dataurl:iter_url,valueField:'value',textField:'text'}]});var vb=new bricks.VBox({padding:'12px',gap:'12px'});vb.add_widget(new bricks.Text({text:'没有当前迭代,请选择一个迭代:',cfontsize:0.95,color:'#64748b'}));vb.add_widget(form);var btn=new bricks.Button({label:'确定',css:'primary'});btn.bind('click',function(){var v=form.getValue();var siid=(v||{}).iteration_id||'';if(!siid){bricks.show_error({title:'提示',message:'请选择一个迭代'});return;}var body='iteration_id='+encodeURIComponent(siid);if(pid)body+='&project_id='+encodeURIComponent(pid);fetch('/pipeline-sdlc/api/cockpit_context_update.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){pw.destroy();var cd=bricks.getWidgetById('context_data',bricks.app);if(cd)cd.render({});}else{bricks.show_error({title:'失败',message:r.error||'保存失败'});}});});vb.add_widget(btn);pw.content_w.add_widget(vb);return;}var box=new bricks.VBox();box.add_widget(new bricks.Text({text:'迭代名称:'+(d.iteration_name||'--'),cfontsize:1.1,fontWeight:'bold',marginBottom:'8px'}));box.add_widget(new bricks.Text({text:'迭代ID'+iid,cfontsize:0.85,color:'#64748b',marginBottom:'4px'}));box.add_widget(new bricks.Text({text:'所属项目:'+(d.project_name||pid),cfontsize:0.85,color:'#64748b'}));var pw=new bricks.PopupWindow({title:'迭代详情',cwidth:36,cheight:14,auto_open:true});pw.content_w.add_widget(box);"
}
]
},