From 82f9fb67641f5d9874d70a7f4fd129534e22358f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 7 Aug 2026 15:06:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE/?= =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E6=8C=89=E9=92=AE=E6=94=B9=E4=B8=BA=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8=E5=BC=B9=E7=AA=97=EF=BC=8C=E6=97=A0=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E6=97=B6=E6=98=BE=E7=A4=BA=E5=88=97=E8=A1=A8=E4=BE=9B?= =?UTF-8?q?=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 当前项目按钮:无项目时弹出选择器,有项目时显示详情 - 当前迭代按钮:无迭代时弹出选择器(按项目过滤),有迭代时显示详情 - 新增 cockpit_project_picker.dspy:返回项目列表 - 新增 cockpit_iteration_picker.dspy:返回迭代列表(支持project_id过滤) - 新增 cockpit_context_update.dspy:保存选择到 pipeline_agent_settings --- wwwroot/api/cockpit_context_update.dspy | 72 +++++++++++++++++++++++ wwwroot/api/cockpit_iteration_picker.dspy | 29 +++++++++ wwwroot/api/cockpit_project_picker.dspy | 19 ++++++ wwwroot/index.ui | 4 +- wwwroot/index_cockpit.ui | 4 +- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 wwwroot/api/cockpit_context_update.dspy create mode 100644 wwwroot/api/cockpit_iteration_picker.dspy create mode 100644 wwwroot/api/cockpit_project_picker.dspy diff --git a/wwwroot/api/cockpit_context_update.dspy b/wwwroot/api/cockpit_context_update.dspy new file mode 100644 index 0000000..d2ba7ef --- /dev/null +++ b/wwwroot/api/cockpit_context_update.dspy @@ -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) diff --git a/wwwroot/api/cockpit_iteration_picker.dspy b/wwwroot/api/cockpit_iteration_picker.dspy new file mode 100644 index 0000000..d47ff77 --- /dev/null +++ b/wwwroot/api/cockpit_iteration_picker.dspy @@ -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 diff --git a/wwwroot/api/cockpit_project_picker.dspy b/wwwroot/api/cockpit_project_picker.dspy new file mode 100644 index 0000000..81c685a --- /dev/null +++ b/wwwroot/api/cockpit_project_picker.dspy @@ -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 diff --git a/wwwroot/index.ui b/wwwroot/index.ui index b561ff1..a641f64 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -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);" } ] }, diff --git a/wwwroot/index_cockpit.ui b/wwwroot/index_cockpit.ui index 180a28f..6752b0a 100644 --- a/wwwroot/index_cockpit.ui +++ b/wwwroot/index_cockpit.ui @@ -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);" } ] },