feat: add 40 dspy APIs, 7 UI pages, CSS, pipeline definition SQL

This commit is contained in:
Hermes Agent 2026-06-16 13:16:24 +08:00
parent 26c092073d
commit f69870e96a
47 changed files with 1404 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

101
wwwroot/index.ui Normal file
View File

@ -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"}
}
]
}

201
wwwroot/pipeline_sdlc.css Normal file
View File

@ -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;
}
}

View File

@ -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"}
}
]
}

View File

@ -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"}}
]
}
]
}
]
}
]
}

View File

@ -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"}
}
]
}

View File

@ -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"}
}
]
}

179
wwwroot/sd_review/index.ui Normal file
View File

@ -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"}}
]
}
]
}