From f69870e96ac693c1aa1a562889214f14ca99e5fb Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 16 Jun 2026 13:16:24 +0800 Subject: [PATCH] feat: add 40 dspy APIs, 7 UI pages, CSS, pipeline definition SQL --- wwwroot/api/bug_close.dspy | 16 ++ wwwroot/api/bug_confirm.dspy | 16 ++ wwwroot/api/check_iteration_bugs.dspy | 22 ++ wwwroot/api/env_verify.dspy | 35 ++++ wwwroot/api/get_iteration_options.dspy | 9 + wwwroot/api/get_project_options.dspy | 9 + wwwroot/api/get_search_bug_status.dspy | 9 + wwwroot/api/get_search_case_status.dspy | 9 + wwwroot/api/get_search_case_type.dspy | 9 + wwwroot/api/get_search_env_status.dspy | 9 + wwwroot/api/get_search_env_type.dspy | 9 + wwwroot/api/get_search_iteration_status.dspy | 9 + wwwroot/api/get_search_iteration_type.dspy | 9 + wwwroot/api/get_search_plan_status.dspy | 9 + wwwroot/api/get_search_plan_type.dspy | 9 + wwwroot/api/get_search_priority.dspy | 9 + wwwroot/api/get_search_project_type.dspy | 9 + wwwroot/api/get_search_reporter_type.dspy | 9 + wwwroot/api/get_search_severity.dspy | 9 + wwwroot/api/get_search_status.dspy | 9 + wwwroot/api/get_test_plan_options.dspy | 9 + wwwroot/api/sd_bug_create.dspy | 27 +++ wwwroot/api/sd_bug_delete.dspy | 14 ++ wwwroot/api/sd_bug_update.dspy | 38 ++++ wwwroot/api/sd_case_create.dspy | 23 +++ wwwroot/api/sd_case_delete.dspy | 14 ++ wwwroot/api/sd_case_update.dspy | 30 +++ wwwroot/api/sd_env_create.dspy | 30 +++ wwwroot/api/sd_env_delete.dspy | 14 ++ wwwroot/api/sd_env_update.dspy | 44 ++++ wwwroot/api/sd_iter_create.dspy | 21 ++ wwwroot/api/sd_iter_delete.dspy | 14 ++ wwwroot/api/sd_iter_update.dspy | 26 +++ wwwroot/api/sd_plan_create.dspy | 24 +++ wwwroot/api/sd_plan_delete.dspy | 14 ++ wwwroot/api/sd_plan_update.dspy | 32 +++ wwwroot/api/sd_proj_create.dspy | 24 +++ wwwroot/api/sd_proj_delete.dspy | 14 ++ wwwroot/api/sd_proj_update.dspy | 30 +++ wwwroot/api/submit_bug.dspy | 30 +++ wwwroot/index.ui | 101 ++++++++++ wwwroot/pipeline_sdlc.css | 201 +++++++++++++++++++ wwwroot/sd_bug_list/index.ui | 14 ++ wwwroot/sd_dashboard/index.ui | 176 ++++++++++++++++ wwwroot/sd_deploy_env_list/index.ui | 14 ++ wwwroot/sd_project_list/index.ui | 14 ++ wwwroot/sd_review/index.ui | 179 +++++++++++++++++ 47 files changed, 1404 insertions(+) create mode 100644 wwwroot/api/bug_close.dspy create mode 100644 wwwroot/api/bug_confirm.dspy create mode 100644 wwwroot/api/check_iteration_bugs.dspy create mode 100644 wwwroot/api/env_verify.dspy create mode 100644 wwwroot/api/get_iteration_options.dspy create mode 100644 wwwroot/api/get_project_options.dspy create mode 100644 wwwroot/api/get_search_bug_status.dspy create mode 100644 wwwroot/api/get_search_case_status.dspy create mode 100644 wwwroot/api/get_search_case_type.dspy create mode 100644 wwwroot/api/get_search_env_status.dspy create mode 100644 wwwroot/api/get_search_env_type.dspy create mode 100644 wwwroot/api/get_search_iteration_status.dspy create mode 100644 wwwroot/api/get_search_iteration_type.dspy create mode 100644 wwwroot/api/get_search_plan_status.dspy create mode 100644 wwwroot/api/get_search_plan_type.dspy create mode 100644 wwwroot/api/get_search_priority.dspy create mode 100644 wwwroot/api/get_search_project_type.dspy create mode 100644 wwwroot/api/get_search_reporter_type.dspy create mode 100644 wwwroot/api/get_search_severity.dspy create mode 100644 wwwroot/api/get_search_status.dspy create mode 100644 wwwroot/api/get_test_plan_options.dspy create mode 100644 wwwroot/api/sd_bug_create.dspy create mode 100644 wwwroot/api/sd_bug_delete.dspy create mode 100644 wwwroot/api/sd_bug_update.dspy create mode 100644 wwwroot/api/sd_case_create.dspy create mode 100644 wwwroot/api/sd_case_delete.dspy create mode 100644 wwwroot/api/sd_case_update.dspy create mode 100644 wwwroot/api/sd_env_create.dspy create mode 100644 wwwroot/api/sd_env_delete.dspy create mode 100644 wwwroot/api/sd_env_update.dspy create mode 100644 wwwroot/api/sd_iter_create.dspy create mode 100644 wwwroot/api/sd_iter_delete.dspy create mode 100644 wwwroot/api/sd_iter_update.dspy create mode 100644 wwwroot/api/sd_plan_create.dspy create mode 100644 wwwroot/api/sd_plan_delete.dspy create mode 100644 wwwroot/api/sd_plan_update.dspy create mode 100644 wwwroot/api/sd_proj_create.dspy create mode 100644 wwwroot/api/sd_proj_delete.dspy create mode 100644 wwwroot/api/sd_proj_update.dspy create mode 100644 wwwroot/api/submit_bug.dspy create mode 100644 wwwroot/index.ui create mode 100644 wwwroot/pipeline_sdlc.css create mode 100644 wwwroot/sd_bug_list/index.ui create mode 100644 wwwroot/sd_dashboard/index.ui create mode 100644 wwwroot/sd_deploy_env_list/index.ui create mode 100644 wwwroot/sd_project_list/index.ui create mode 100644 wwwroot/sd_review/index.ui diff --git a/wwwroot/api/bug_close.dspy b/wwwroot/api/bug_close.dspy new file mode 100644 index 0000000..6677b6e --- /dev/null +++ b/wwwroot/api/bug_close.dspy @@ -0,0 +1,16 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'status': 'closed', 'closed_at': curDateString(), 'updated_at': curDateString()} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_bugs', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/bug_confirm.dspy b/wwwroot/api/bug_confirm.dspy new file mode 100644 index 0000000..63d52fb --- /dev/null +++ b/wwwroot/api/bug_confirm.dspy @@ -0,0 +1,16 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'status': 'confirmed', 'updated_at': curDateString()} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_bugs', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/check_iteration_bugs.dspy b/wwwroot/api/check_iteration_bugs.dspy new file mode 100644 index 0000000..f415e36 --- /dev/null +++ b/wwwroot/api/check_iteration_bugs.dspy @@ -0,0 +1,22 @@ +iteration_id = params_kw.get('iteration_id', '') +if not iteration_id: + return json.dumps({'status': 'error', 'message': '缺少iteration_id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + total = await sor.sqlExe("select count(*) as cnt from sd_bugs where iteration_id=${iteration_id}$", {'iteration_id': iteration_id}) + closed = await sor.sqlExe("select count(*) as cnt from sd_bugs where iteration_id=${iteration_id}$ and status=${status}$", {'iteration_id': iteration_id, 'status': 'closed'}) + + total_count = total[0]['cnt'] if total else 0 + closed_count = closed[0]['cnt'] if closed else 0 + all_closed = total_count > 0 and total_count == closed_count + + return json.dumps({ + 'status': 'ok', + 'total': total_count, + 'closed': closed_count, + 'all_closed': all_closed, + 'message': f'共{total_count}个bug,已关闭{closed_count}个' + (',全部关闭' if all_closed else ',仍有未关闭') + }, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/env_verify.dspy b/wwwroot/api/env_verify.dspy new file mode 100644 index 0000000..41dc118 --- /dev/null +++ b/wwwroot/api/env_verify.dspy @@ -0,0 +1,35 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select host, port, user, ssh_key_path from sd_deploy_envs where id=${id}$", {'id': rec_id}) + if not rows: + return json.dumps({'status': 'error', 'message': '环境记录不存在'}, ensure_ascii=False) + env = rows[0] if isinstance(rows, list) else rows + + host = env.get('host', '') + port = env.get('port', '22') + ssh_user = env.get('user', '') + ssh_key = env.get('ssh_key_path', '') + + import asyncio + cmd = f"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -p {port}" + if ssh_key: + cmd += f" -i {ssh_key}" + cmd += f" {ssh_user}@{host} echo ok" + + proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=15) + + if proc.returncode == 0: + return json.dumps({'status': 'ok', 'message': '连接成功'}, ensure_ascii=False) + else: + return json.dumps({'status': 'error', 'message': f'连接失败: {stderr.decode().strip()}'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/get_iteration_options.dspy b/wwwroot/api/get_iteration_options.dspy new file mode 100644 index 0000000..217383a --- /dev/null +++ b/wwwroot/api/get_iteration_options.dspy @@ -0,0 +1,9 @@ +result = [] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select id as iteration_id, iteration_name as iteration_id_text from sd_iterations order by iteration_name", {}) + result = list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'dropdown error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_project_options.dspy b/wwwroot/api/get_project_options.dspy new file mode 100644 index 0000000..52107a9 --- /dev/null +++ b/wwwroot/api/get_project_options.dspy @@ -0,0 +1,9 @@ +result = [] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select id as project_id, name as project_id_text from sd_projects order by name", {}) + result = list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'dropdown error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_bug_status.dspy b/wwwroot/api/get_search_bug_status.dspy new file mode 100644 index 0000000..d3f0daf --- /dev/null +++ b/wwwroot/api/get_search_bug_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_bug_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_case_status.dspy b/wwwroot/api/get_search_case_status.dspy new file mode 100644 index 0000000..9e1a1a0 --- /dev/null +++ b/wwwroot/api/get_search_case_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_test_case_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_case_type.dspy b/wwwroot/api/get_search_case_type.dspy new file mode 100644 index 0000000..7c34923 --- /dev/null +++ b/wwwroot/api/get_search_case_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_test_case_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_env_status.dspy b/wwwroot/api/get_search_env_status.dspy new file mode 100644 index 0000000..7c22296 --- /dev/null +++ b/wwwroot/api/get_search_env_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_env_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_env_type.dspy b/wwwroot/api/get_search_env_type.dspy new file mode 100644 index 0000000..349d561 --- /dev/null +++ b/wwwroot/api/get_search_env_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_env_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_iteration_status.dspy b/wwwroot/api/get_search_iteration_status.dspy new file mode 100644 index 0000000..0ab8863 --- /dev/null +++ b/wwwroot/api/get_search_iteration_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_iteration_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_iteration_type.dspy b/wwwroot/api/get_search_iteration_type.dspy new file mode 100644 index 0000000..07fbabc --- /dev/null +++ b/wwwroot/api/get_search_iteration_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_iteration_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_plan_status.dspy b/wwwroot/api/get_search_plan_status.dspy new file mode 100644 index 0000000..328fbd8 --- /dev/null +++ b/wwwroot/api/get_search_plan_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_test_plan_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_plan_type.dspy b/wwwroot/api/get_search_plan_type.dspy new file mode 100644 index 0000000..8ee8a50 --- /dev/null +++ b/wwwroot/api/get_search_plan_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_test_plan_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_priority.dspy b/wwwroot/api/get_search_priority.dspy new file mode 100644 index 0000000..7a0802c --- /dev/null +++ b/wwwroot/api/get_search_priority.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_priority'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_project_type.dspy b/wwwroot/api/get_search_project_type.dspy new file mode 100644 index 0000000..344c0ad --- /dev/null +++ b/wwwroot/api/get_search_project_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_project_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_reporter_type.dspy b/wwwroot/api/get_search_reporter_type.dspy new file mode 100644 index 0000000..d3d28d9 --- /dev/null +++ b/wwwroot/api/get_search_reporter_type.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_reporter_type'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_severity.dspy b/wwwroot/api/get_search_severity.dspy new file mode 100644 index 0000000..7f1ee3e --- /dev/null +++ b/wwwroot/api/get_search_severity.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_bug_severity'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_status.dspy b/wwwroot/api/get_search_status.dspy new file mode 100644 index 0000000..8550e52 --- /dev/null +++ b/wwwroot/api/get_search_status.dspy @@ -0,0 +1,9 @@ +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid=${parentid}$ order by v", {'parentid': 'sd_project_status'}) + result = [{'value': '', 'text': '全部'}] + list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'search error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_test_plan_options.dspy b/wwwroot/api/get_test_plan_options.dspy new file mode 100644 index 0000000..624271e --- /dev/null +++ b/wwwroot/api/get_test_plan_options.dspy @@ -0,0 +1,9 @@ +result = [] +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + rows = await sor.sqlExe("select id as plan_id, plan_name as plan_id_text from sd_test_plans order by plan_name", {}) + result = list(rows) + return json.dumps(result, ensure_ascii=False) +except Exception as e: + debug(f'dropdown error: {e}') +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/sd_bug_create.dspy b/wwwroot/api/sd_bug_create.dspy new file mode 100644 index 0000000..922b6ec --- /dev/null +++ b/wwwroot/api/sd_bug_create.dspy @@ -0,0 +1,27 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + + +data = { + 'id': getID(), + 'iteration_id': params_kw.get('iteration_id', ''), + 'case_id': params_kw.get('case_id', ''), + 'step_name': params_kw.get('step_name', ''), + 'title': params_kw.get('title', ''), + 'description': params_kw.get('description', ''), + 'severity': params_kw.get('severity', ''), + 'priority': params_kw.get('priority', ''), + 'status': params_kw.get('status', ''), + 'reporter_type': params_kw.get('reporter_type', ''), + 'reporter_id': params_kw.get('reporter_id', ''), + 'assignee_id': params_kw.get('assignee_id', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_bugs', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_bug_delete.dspy b/wwwroot/api/sd_bug_delete.dspy new file mode 100644 index 0000000..79620dc --- /dev/null +++ b/wwwroot/api/sd_bug_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_bugs', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_bug_update.dspy b/wwwroot/api/sd_bug_update.dspy new file mode 100644 index 0000000..03d022a --- /dev/null +++ b/wwwroot/api/sd_bug_update.dspy @@ -0,0 +1,38 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'iteration_id' in params_kw: + data['iteration_id'] = params_kw['iteration_id'] +if 'case_id' in params_kw: + data['case_id'] = params_kw['case_id'] +if 'step_name' in params_kw: + data['step_name'] = params_kw['step_name'] +if 'title' in params_kw: + data['title'] = params_kw['title'] +if 'description' in params_kw: + data['description'] = params_kw['description'] +if 'severity' in params_kw: + data['severity'] = params_kw['severity'] +if 'priority' in params_kw: + data['priority'] = params_kw['priority'] +if 'status' in params_kw: + data['status'] = params_kw['status'] +if 'reporter_type' in params_kw: + data['reporter_type'] = params_kw['reporter_type'] +if 'reporter_id' in params_kw: + data['reporter_id'] = params_kw['reporter_id'] +if 'assignee_id' in params_kw: + data['assignee_id'] = params_kw['assignee_id'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_bugs', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_case_create.dspy b/wwwroot/api/sd_case_create.dspy new file mode 100644 index 0000000..bd5c812 --- /dev/null +++ b/wwwroot/api/sd_case_create.dspy @@ -0,0 +1,23 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + + +data = { + 'id': getID(), + 'plan_id': params_kw.get('plan_id', ''), + 'case_name': params_kw.get('case_name', ''), + 'case_type': params_kw.get('case_type', ''), + 'priority': params_kw.get('priority', ''), + 'precondition': params_kw.get('precondition', ''), + 'steps': params_kw.get('steps', ''), + 'expected_result': params_kw.get('expected_result', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_test_cases', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_case_delete.dspy b/wwwroot/api/sd_case_delete.dspy new file mode 100644 index 0000000..3c005cb --- /dev/null +++ b/wwwroot/api/sd_case_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_test_cases', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_case_update.dspy b/wwwroot/api/sd_case_update.dspy new file mode 100644 index 0000000..e936ced --- /dev/null +++ b/wwwroot/api/sd_case_update.dspy @@ -0,0 +1,30 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'plan_id' in params_kw: + data['plan_id'] = params_kw['plan_id'] +if 'case_name' in params_kw: + data['case_name'] = params_kw['case_name'] +if 'case_type' in params_kw: + data['case_type'] = params_kw['case_type'] +if 'priority' in params_kw: + data['priority'] = params_kw['priority'] +if 'precondition' in params_kw: + data['precondition'] = params_kw['precondition'] +if 'steps' in params_kw: + data['steps'] = params_kw['steps'] +if 'expected_result' in params_kw: + data['expected_result'] = params_kw['expected_result'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_test_cases', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_env_create.dspy b/wwwroot/api/sd_env_create.dspy new file mode 100644 index 0000000..63d0f4c --- /dev/null +++ b/wwwroot/api/sd_env_create.dspy @@ -0,0 +1,30 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + + +data = { + 'id': getID(), + 'project_id': params_kw.get('project_id', ''), + 'env_type': params_kw.get('env_type', ''), + 'host': params_kw.get('host', ''), + 'port': params_kw.get('port', ''), + 'user': params_kw.get('user', ''), + 'ssh_key_path': params_kw.get('ssh_key_path', ''), + 'sudo_enabled': params_kw.get('sudo_enabled', ''), + 'deploy_path': params_kw.get('deploy_path', ''), + 'python_path': params_kw.get('python_path', ''), + 'db_host': params_kw.get('db_host', ''), + 'db_port': params_kw.get('db_port', ''), + 'db_name': params_kw.get('db_name', ''), + 'db_user': params_kw.get('db_user', ''), + 'db_password': params_kw.get('db_password', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_deploy_envs', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_env_delete.dspy b/wwwroot/api/sd_env_delete.dspy new file mode 100644 index 0000000..3c5e3f9 --- /dev/null +++ b/wwwroot/api/sd_env_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_deploy_envs', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_env_update.dspy b/wwwroot/api/sd_env_update.dspy new file mode 100644 index 0000000..b8fc10e --- /dev/null +++ b/wwwroot/api/sd_env_update.dspy @@ -0,0 +1,44 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'project_id' in params_kw: + data['project_id'] = params_kw['project_id'] +if 'env_type' in params_kw: + data['env_type'] = params_kw['env_type'] +if 'host' in params_kw: + data['host'] = params_kw['host'] +if 'port' in params_kw: + data['port'] = params_kw['port'] +if 'user' in params_kw: + data['user'] = params_kw['user'] +if 'ssh_key_path' in params_kw: + data['ssh_key_path'] = params_kw['ssh_key_path'] +if 'sudo_enabled' in params_kw: + data['sudo_enabled'] = params_kw['sudo_enabled'] +if 'deploy_path' in params_kw: + data['deploy_path'] = params_kw['deploy_path'] +if 'python_path' in params_kw: + data['python_path'] = params_kw['python_path'] +if 'db_host' in params_kw: + data['db_host'] = params_kw['db_host'] +if 'db_port' in params_kw: + data['db_port'] = params_kw['db_port'] +if 'db_name' in params_kw: + data['db_name'] = params_kw['db_name'] +if 'db_user' in params_kw: + data['db_user'] = params_kw['db_user'] +if 'db_password' in params_kw: + data['db_password'] = params_kw['db_password'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_deploy_envs', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_iter_create.dspy b/wwwroot/api/sd_iter_create.dspy new file mode 100644 index 0000000..6031ebb --- /dev/null +++ b/wwwroot/api/sd_iter_create.dspy @@ -0,0 +1,21 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + + +data = { + 'id': getID(), + 'project_id': params_kw.get('project_id', ''), + 'iteration_name': params_kw.get('iteration_name', ''), + 'iteration_type': params_kw.get('iteration_type', ''), + 'scope': params_kw.get('scope', ''), + 'priority': params_kw.get('priority', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_iterations', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_iter_delete.dspy b/wwwroot/api/sd_iter_delete.dspy new file mode 100644 index 0000000..5befa7a --- /dev/null +++ b/wwwroot/api/sd_iter_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_iterations', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_iter_update.dspy b/wwwroot/api/sd_iter_update.dspy new file mode 100644 index 0000000..49fc7f9 --- /dev/null +++ b/wwwroot/api/sd_iter_update.dspy @@ -0,0 +1,26 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'project_id' in params_kw: + data['project_id'] = params_kw['project_id'] +if 'iteration_name' in params_kw: + data['iteration_name'] = params_kw['iteration_name'] +if 'iteration_type' in params_kw: + data['iteration_type'] = params_kw['iteration_type'] +if 'scope' in params_kw: + data['scope'] = params_kw['scope'] +if 'priority' in params_kw: + data['priority'] = params_kw['priority'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_iterations', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_plan_create.dspy b/wwwroot/api/sd_plan_create.dspy new file mode 100644 index 0000000..73be583 --- /dev/null +++ b/wwwroot/api/sd_plan_create.dspy @@ -0,0 +1,24 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + + +data = { + 'id': getID(), + 'iteration_id': params_kw.get('iteration_id', ''), + 'plan_name': params_kw.get('plan_name', ''), + 'plan_type': params_kw.get('plan_type', ''), + 'scope': params_kw.get('scope', ''), + 'environment': params_kw.get('environment', ''), + 'entry_criteria': params_kw.get('entry_criteria', ''), + 'exit_criteria': params_kw.get('exit_criteria', ''), + 'status': params_kw.get('status', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_test_plans', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_plan_delete.dspy b/wwwroot/api/sd_plan_delete.dspy new file mode 100644 index 0000000..0f1b791 --- /dev/null +++ b/wwwroot/api/sd_plan_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_test_plans', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_plan_update.dspy b/wwwroot/api/sd_plan_update.dspy new file mode 100644 index 0000000..72f9cf8 --- /dev/null +++ b/wwwroot/api/sd_plan_update.dspy @@ -0,0 +1,32 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'iteration_id' in params_kw: + data['iteration_id'] = params_kw['iteration_id'] +if 'plan_name' in params_kw: + data['plan_name'] = params_kw['plan_name'] +if 'plan_type' in params_kw: + data['plan_type'] = params_kw['plan_type'] +if 'scope' in params_kw: + data['scope'] = params_kw['scope'] +if 'environment' in params_kw: + data['environment'] = params_kw['environment'] +if 'entry_criteria' in params_kw: + data['entry_criteria'] = params_kw['entry_criteria'] +if 'exit_criteria' in params_kw: + data['exit_criteria'] = params_kw['exit_criteria'] +if 'status' in params_kw: + data['status'] = params_kw['status'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_test_plans', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_proj_create.dspy b/wwwroot/api/sd_proj_create.dspy new file mode 100644 index 0000000..7319def --- /dev/null +++ b/wwwroot/api/sd_proj_create.dspy @@ -0,0 +1,24 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +org_id = (await get_userorgid()) or '0' +data = { + 'id': getID(), + 'name': params_kw.get('name', ''), + 'description': params_kw.get('description', ''), + 'project_type': params_kw.get('project_type', ''), + 'tech_stack': params_kw.get('tech_stack', ''), + 'repo_url': params_kw.get('repo_url', ''), + 'pipeline_id': params_kw.get('pipeline_id', ''), + 'status': params_kw.get('status', ''), + 'org_id': org_id, + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_projects', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_proj_delete.dspy b/wwwroot/api/sd_proj_delete.dspy new file mode 100644 index 0000000..78a2b0e --- /dev/null +++ b/wwwroot/api/sd_proj_delete.dspy @@ -0,0 +1,14 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.D('sd_projects', {'id': rec_id}) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/sd_proj_update.dspy b/wwwroot/api/sd_proj_update.dspy new file mode 100644 index 0000000..536c1c3 --- /dev/null +++ b/wwwroot/api/sd_proj_update.dspy @@ -0,0 +1,30 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +rec_id = params_kw.get('id', '') +if not rec_id: + return json.dumps({'status': 'error', 'message': '缺少id'}, ensure_ascii=False) + +data = {'id': rec_id, 'updated_at': curDateString()} +if 'name' in params_kw: + data['name'] = params_kw['name'] +if 'description' in params_kw: + data['description'] = params_kw['description'] +if 'project_type' in params_kw: + data['project_type'] = params_kw['project_type'] +if 'tech_stack' in params_kw: + data['tech_stack'] = params_kw['tech_stack'] +if 'repo_url' in params_kw: + data['repo_url'] = params_kw['repo_url'] +if 'pipeline_id' in params_kw: + data['pipeline_id'] = params_kw['pipeline_id'] +if 'status' in params_kw: + data['status'] = params_kw['status'] + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.U('sd_projects', data) + return json.dumps({'status': 'ok'}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/api/submit_bug.dspy b/wwwroot/api/submit_bug.dspy new file mode 100644 index 0000000..46b15fd --- /dev/null +++ b/wwwroot/api/submit_bug.dspy @@ -0,0 +1,30 @@ +user_id = await get_user() +if not user_id: + return json.dumps({'status': 'error', 'message': '未登录'}, ensure_ascii=False) + +reporter_type = params_kw.get('reporter_type', '') +if not reporter_type: + reporter_type = 'agent' if params_kw.get('is_agent', '') else 'human' + +data = { + 'id': getID(), + 'iteration_id': params_kw.get('iteration_id', ''), + 'case_id': params_kw.get('case_id', ''), + 'step_name': params_kw.get('step_name', ''), + 'title': params_kw.get('title', ''), + 'description': params_kw.get('description', ''), + 'severity': params_kw.get('severity', 'medium'), + 'priority': params_kw.get('priority', 'medium'), + 'status': 'new', + 'reporter_type': reporter_type, + 'reporter_id': user_id, + 'assignee_id': params_kw.get('assignee_id', ''), + 'created_at': curDateString(), +} + +try: + async with get_sor_context(request._run_ns, 'pipeline') as sor: + await sor.C('sd_bugs', data) + return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) +except Exception as e: + return json.dumps({'status': 'error', 'message': str(e)}, ensure_ascii=False) diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..1c16b76 --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,101 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "20px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "SDLC Pipeline", "cfontsize": 28, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "软件开发生命周期管理", "cfontsize": 14, "color": "#888888"} + }, + { + "widgettype": "ResponsableBox", + "options": {"gap": "16px", "minWidth": "220px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "cursor": "pointer", "cwidth": 25, "gap": "12px", "border": "1px solid #2A2A3E"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_content", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_project_list/index.ui')}}"} + } + ], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "📁", "cfontsize": 32}}, + {"widgettype": "Text", "options": {"text": "项目管理", "cfontsize": 18, "color": "#FFFFFF", "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": "Project Management", "cfontsize": 12, "color": "#4A90D9"}}, + {"widgettype": "Text", "options": {"text": "管理所有软件项目的创建、配置和状态跟踪", "cfontsize": 13, "color": "#888888"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "cursor": "pointer", "cwidth": 25, "gap": "12px", "border": "1px solid #2A2A3E"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_content", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_iteration/index.ui')}}"} + } + ], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🔄", "cfontsize": 32}}, + {"widgettype": "Text", "options": {"text": "迭代看板", "cfontsize": 18, "color": "#FFFFFF", "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": "Iteration Board", "cfontsize": 12, "color": "#4A90D9"}}, + {"widgettype": "Text", "options": {"text": "可视化迭代进度,管理Sprint和任务分配", "cfontsize": 13, "color": "#888888"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "cursor": "pointer", "cwidth": 25, "gap": "12px", "border": "1px solid #2A2A3E"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_content", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_bug_list/index.ui')}}"} + } + ], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🐛", "cfontsize": 32}}, + {"widgettype": "Text", "options": {"text": "Bug管理", "cfontsize": 18, "color": "#FFFFFF", "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": "Bug Management", "cfontsize": 12, "color": "#4A90D9"}}, + {"widgettype": "Text", "options": {"text": "跟踪和管理项目缺陷,分配修复任务", "cfontsize": 13, "color": "#888888"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "cursor": "pointer", "cwidth": 25, "gap": "12px", "border": "1px solid #2A2A3E"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_content", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_deploy_env_list/index.ui')}}"} + } + ], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🚀", "cfontsize": 32}}, + {"widgettype": "Text", "options": {"text": "部署环境", "cfontsize": 18, "color": "#FFFFFF", "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": "Deploy Environments", "cfontsize": 12, "color": "#4A90D9"}}, + {"widgettype": "Text", "options": {"text": "配置和管理开发、测试、生产部署环境", "cfontsize": 13, "color": "#888888"}} + ] + } + ] + }, + { + "widgettype": "VBox", + "id": "app.sdlc_content", + "options": {"width": "100%", "flex": "1", "minHeight": "200px"} + } + ] +} diff --git a/wwwroot/pipeline_sdlc.css b/wwwroot/pipeline_sdlc.css new file mode 100644 index 0000000..b067743 --- /dev/null +++ b/wwwroot/pipeline_sdlc.css @@ -0,0 +1,201 @@ +/* pipeline_sdlc.css - SDLC Pipeline Module Custom Styles */ +/* Dark theme compatible */ + +/* === Base Colors === */ +:root { + --sdlc-bg-primary: #121212; + --sdlc-bg-card: #1E1E2E; + --sdlc-bg-card-hover: #252540; + --sdlc-border: #2A2A3E; + --sdlc-border-hover: #3A3A5E; + --sdlc-text-primary: #E0E0E0; + --sdlc-text-secondary: #888888; + --sdlc-text-muted: #666666; + --sdlc-accent: #4A90D9; + --sdlc-success: #4AD97A; + --sdlc-warning: #D9A04A; + --sdlc-danger: #D94A4A; + --sdlc-purple: #9A4AD9; +} + +/* === Card Styles === */ +.sdlc-card { + background-color: var(--sdlc-bg-card); + border: 1px solid var(--sdlc-border); + border-radius: 8px; + padding: 20px; + transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.1s ease; +} + +.sdlc-card:hover { + background-color: var(--sdlc-bg-card-hover); + border-color: var(--sdlc-border-hover); + transform: translateY(-2px); +} + +/* === Status Indicators === */ +.sdlc-status-planning { + color: var(--sdlc-accent); +} + +.sdlc-status-in-progress { + color: var(--sdlc-success); +} + +.sdlc-status-completed { + color: var(--sdlc-text-secondary); +} + +.sdlc-status-blocked { + color: var(--sdlc-danger); +} + +/* === Priority Indicators === */ +.sdlc-priority-critical { + color: var(--sdlc-danger); + font-weight: bold; +} + +.sdlc-priority-high { + color: var(--sdlc-warning); + font-weight: bold; +} + +.sdlc-priority-medium { + color: var(--sdlc-accent); +} + +.sdlc-priority-low { + color: var(--sdlc-text-secondary); +} + +/* === Kanban Column Headers === */ +.sdlc-kanban-header-planning { + background-color: #1A3A5C; + padding: 12px; + border-radius: 6px; +} + +.sdlc-kanban-header-progress { + background-color: #2A4A1A; + padding: 12px; + border-radius: 6px; +} + +.sdlc-kanban-header-completed { + background-color: #3A3A3A; + padding: 12px; + border-radius: 6px; +} + +/* === Review Queue === */ +.sdlc-review-task { + background-color: var(--sdlc-bg-card); + border: 1px solid var(--sdlc-border); + border-radius: 8px; + padding: 16px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.sdlc-review-task:hover { + background-color: var(--sdlc-bg-card-hover); +} + +.sdlc-approval-gate { + background-color: var(--sdlc-bg-card); + border-left: 3px solid var(--sdlc-purple); + border-radius: 8px; + padding: 16px; +} + +/* === Dashboard Stat Cards === */ +.sdlc-stat-card { + background-color: var(--sdlc-bg-card); + border: 1px solid var(--sdlc-border); + border-radius: 8px; + padding: 20px; + text-align: center; +} + +.sdlc-stat-number { + font-size: 36px; + font-weight: bold; +} + +.sdlc-stat-label { + font-size: 14px; + color: var(--sdlc-text-secondary); + margin-top: 4px; +} + +/* === Iteration Cards === */ +.sdlc-iteration-card { + background-color: var(--sdlc-bg-card); + border: 1px solid var(--sdlc-border); + border-radius: 8px; + padding: 16px; + cursor: pointer; + transition: all 0.2s ease; +} + +.sdlc-iteration-card:hover { + background-color: var(--sdlc-bg-card-hover); + border-color: var(--sdlc-accent); +} + +.sdlc-iteration-completed { + opacity: 0.7; +} + +/* === Deployment Status === */ +.sdlc-deploy-success { + color: var(--sdlc-success); +} + +.sdlc-deploy-pending { + color: var(--sdlc-warning); +} + +.sdlc-deploy-failed { + color: var(--sdlc-danger); +} + +.sdlc-deploy-dev { + color: var(--sdlc-accent); +} + +/* === Scrollbar Styling (Dark Theme) === */ +.sdlc-scrollable::-webkit-scrollbar { + width: 8px; +} + +.sdlc-scrollable::-webkit-scrollbar-track { + background: var(--sdlc-bg-primary); +} + +.sdlc-scrollable::-webkit-scrollbar-thumb { + background: var(--sdlc-border); + border-radius: 4px; +} + +.sdlc-scrollable::-webkit-scrollbar-thumb:hover { + background: var(--sdlc-border-hover); +} + +/* === Animation === */ +@keyframes sdlc-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.sdlc-pending-indicator { + animation: sdlc-pulse 2s ease-in-out infinite; +} + +/* === Responsive Adjustments === */ +@media (max-width: 768px) { + .sdlc-stat-number { + font-size: 28px; + } +} diff --git a/wwwroot/sd_bug_list/index.ui b/wwwroot/sd_bug_list/index.ui new file mode 100644 index 0000000..ff0dd40 --- /dev/null +++ b/wwwroot/sd_bug_list/index.ui @@ -0,0 +1,14 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "16px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "Bug管理 - CRUD List", "cfontsize": 20, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "Generated by xls2crud - This page will be auto-generated from the sd_bug_list table definition", "cfontsize": 14, "color": "#888888"} + } + ] +} diff --git a/wwwroot/sd_dashboard/index.ui b/wwwroot/sd_dashboard/index.ui new file mode 100644 index 0000000..a2b4baa --- /dev/null +++ b/wwwroot/sd_dashboard/index.ui @@ -0,0 +1,176 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "20px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "📊 项目概览 Dashboard", "cfontsize": 24, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "软件开发生命周期全局统计", "cfontsize": 14, "color": "#888888"} + }, + { + "widgettype": "ResponsableBox", + "options": {"gap": "16px", "minWidth": "180px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "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": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "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": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "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": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "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": "ResponsableBox", + "options": {"gap": "16px", "minWidth": "350px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "12px", "border": "1px solid #2A2A3E", "cwidth": 50}, + "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": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "20px", "borderRadius": "8px", "gap": "12px", "border": "1px solid #2A2A3E", "cwidth": 50}, + "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"}} + ] + } + ] + } + ] + } + ] +} diff --git a/wwwroot/sd_deploy_env_list/index.ui b/wwwroot/sd_deploy_env_list/index.ui new file mode 100644 index 0000000..45e4ef0 --- /dev/null +++ b/wwwroot/sd_deploy_env_list/index.ui @@ -0,0 +1,14 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "16px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "部署环境 - CRUD List", "cfontsize": 20, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "Generated by xls2crud - This page will be auto-generated from the sd_deploy_env_list table definition", "cfontsize": 14, "color": "#888888"} + } + ] +} diff --git a/wwwroot/sd_project_list/index.ui b/wwwroot/sd_project_list/index.ui new file mode 100644 index 0000000..7cc5fc6 --- /dev/null +++ b/wwwroot/sd_project_list/index.ui @@ -0,0 +1,14 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "16px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "项目管理 - CRUD List", "cfontsize": 20, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "Generated by xls2crud - This page will be auto-generated from the sd_project_list table definition", "cfontsize": 14, "color": "#888888"} + } + ] +} diff --git a/wwwroot/sd_review/index.ui b/wwwroot/sd_review/index.ui new file mode 100644 index 0000000..75d0149 --- /dev/null +++ b/wwwroot/sd_review/index.ui @@ -0,0 +1,179 @@ +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px", "gap": "20px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "📝 审核队列 Review Queue", "cfontsize": 24, "color": "#E0E0E0", "fontWeight": "bold"} + }, + { + "widgettype": "Text", + "options": {"text": "待处理的人工任务与审批门禁", "cfontsize": 14, "color": "#888888"} + }, + { + "widgettype": "ResponsableBox", + "options": {"gap": "16px", "minWidth": "400px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"cwidth": 50, "gap": "12px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"backgroundColor": "#3A2A1A", "padding": "12px", "borderRadius": "6px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🔔 人工任务 Human Tasks", "cfontsize": 16, "color": "#D9A04A", "fontWeight": "bold"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "16px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cursor": "pointer"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_review_detail", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_review/detail.ui')}}?task_id=1"} + } + ], + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "⏳", "cfontsize": 16}}, + {"widgettype": "Text", "options": {"text": "代码审查 - API重构PR #42", "cfontsize": 14, "color": "#E0E0E0", "fontWeight": "bold"}} + ] + }, + {"widgettype": "Text", "options": {"text": "指派: 张三 · 优先级: 高", "cfontsize": 12, "color": "#D9A04A"}}, + {"widgettype": "Text", "options": {"text": "提交时间: 2026-06-15 14:30 · 等待 1天", "cfontsize": 12, "color": "#888888"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "16px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cursor": "pointer"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_review_detail", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_review/detail.ui')}}?task_id=2"} + } + ], + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "⏳", "cfontsize": 16}}, + {"widgettype": "Text", "options": {"text": "数据库迁移确认 - v2.3.0", "cfontsize": 14, "color": "#E0E0E0", "fontWeight": "bold"}} + ] + }, + {"widgettype": "Text", "options": {"text": "指派: 李四 · 优先级: 紧急", "cfontsize": 12, "color": "#D94A4A"}}, + {"widgettype": "Text", "options": {"text": "提交时间: 2026-06-16 09:15 · 等待 2小时", "cfontsize": 12, "color": "#888888"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "16px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cursor": "pointer"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_review_detail", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_review/detail.ui')}}?task_id=3"} + } + ], + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "⏳", "cfontsize": 16}}, + {"widgettype": "Text", "options": {"text": "安全扫描确认 - 依赖更新", "cfontsize": 14, "color": "#E0E0E0", "fontWeight": "bold"}} + ] + }, + {"widgettype": "Text", "options": {"text": "指派: 王五 · 优先级: 中", "cfontsize": 12, "color": "#4A90D9"}}, + {"widgettype": "Text", "options": {"text": "提交时间: 2026-06-14 16:00 · 等待 3天", "cfontsize": 12, "color": "#888888"}} + ] + } + ] + }, + { + "widgettype": "VBox", + "options": {"cwidth": 50, "gap": "12px"}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"backgroundColor": "#2A1A3A", "padding": "12px", "borderRadius": "6px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🚧 审批门禁 Approval Gates", "cfontsize": 16, "color": "#9A4AD9", "fontWeight": "bold"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "16px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cursor": "pointer"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_review_detail", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_review/detail.ui')}}?gate_id=1"} + } + ], + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🔒", "cfontsize": 16}}, + {"widgettype": "Text", "options": {"text": "生产部署审批 - v2.3.0-rc1", "cfontsize": 14, "color": "#E0E0E0", "fontWeight": "bold"}} + ] + }, + {"widgettype": "Text", "options": {"text": "需要 2/3 审批人通过 · 当前 1/3", "cfontsize": 12, "color": "#9A4AD9"}}, + {"widgettype": "Text", "options": {"text": "流水线: deploy-production · 阻塞中", "cfontsize": 12, "color": "#D94A4A"}} + ] + }, + { + "widgettype": "VBox", + "options": {"backgroundColor": "#1E1E2E", "padding": "16px", "borderRadius": "8px", "gap": "8px", "border": "1px solid #2A2A3E", "cursor": "pointer"}, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.sdlc_review_detail", + "options": {"url": "{{entire_url('pipeline_sdlc/sd_review/detail.ui')}}?gate_id=2"} + } + ], + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🔒", "cfontsize": 16}}, + {"widgettype": "Text", "options": {"text": "架构变更审批 - 新增缓存层", "cfontsize": 14, "color": "#E0E0E0", "fontWeight": "bold"}} + ] + }, + {"widgettype": "Text", "options": {"text": "需要架构委员会审批 · 等待中", "cfontsize": 12, "color": "#9A4AD9"}}, + {"widgettype": "Text", "options": {"text": "流水线: design-review · 阻塞中", "cfontsize": 12, "color": "#D94A4A"}} + ] + } + ] + } + ] + }, + { + "widgettype": "VBox", + "id": "app.sdlc_review_detail", + "options": {"width": "100%", "flex": "1", "minHeight": "100px", "backgroundColor": "#1E1E2E", "borderRadius": "8px", "padding": "16px", "border": "1px solid #2A2A3E"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "点击任务或审批门禁查看详情", "cfontsize": 14, "color": "#666666"}} + ] + } + ] +}