alphax/static/config.html
2026-06-07 20:58:35 +08:00

64 lines
9.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}AlphaX Agent — 配置中心{% endblock %}
{% block extra_head_css %}
<style>
.shell{width:min(100% - 40px,1280px);margin:0 auto;padding:24px 0 44px}.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin-bottom:16px;flex-wrap:wrap}.page-head h1{font-size:28px;font-weight:950;letter-spacing:-.6px;color:var(--ink)}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.55;max-width:860px}.actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.btn,.select{height:38px;border:1px solid var(--hairline-strong);background:var(--canvas);border-radius:var(--radius-md);padding:0 12px;font-size:13px;font-weight:850;color:var(--ink)}.btn{cursor:pointer}.layout{display:grid;grid-template-columns:380px minmax(0,1fr);gap:14px;align-items:start}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);overflow:hidden;min-width:0}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:13px 14px;border-bottom:1px solid var(--hairline-soft)}.panel-title{font-size:14px;font-weight:950;color:var(--ink)}.panel-note{font-size:11px;color:var(--stone);font-weight:850}.config-list{display:grid}.config-row{padding:12px 14px;border-bottom:1px solid var(--hairline-soft);cursor:pointer;transition:.12s}.config-row:hover{background:var(--surface)}.config-row.active{background:rgba(66,98,255,.06);box-shadow:inset 3px 0 0 var(--blue)}.key{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:950;color:var(--ink);font-size:13px}.meta{margin-top:5px;color:var(--stone);font-size:11px;line-height:1.45}.badge{display:inline-flex;height:22px;align-items:center;border-radius:999px;border:1px solid var(--hairline-soft);background:var(--surface);padding:0 8px;font-size:11px;font-weight:900;color:var(--slate)}.badge.strategy{color:var(--blue);background:rgba(66,98,255,.07);border-color:rgba(66,98,255,.16)}.badge.system{color:var(--green);background:var(--green-light);border-color:rgba(0,180,115,.18)}.editor{padding:14px}.field{margin-bottom:12px}.field label{display:block;font-size:11px;font-weight:900;color:var(--stone);margin-bottom:6px}.input,.textarea{width:100%;border:1px solid var(--hairline-strong);background:var(--canvas);border-radius:var(--radius-md);padding:10px 12px;font-size:13px;color:var(--ink);outline:none}.textarea{min-height:420px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.55;resize:vertical}.msg{font-size:12px;min-height:18px;margin-top:8px}.msg.ok{color:var(--green)}.msg.err{color:var(--red)}.empty,.loading{padding:34px 16px;text-align:center;color:var(--stone);font-size:13px}.hint{padding:11px 12px;border:1px solid rgba(66,98,255,.14);background:rgba(66,98,255,.045);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.55;margin-bottom:14px}@media(max-width:980px){.layout{grid-template-columns:1fr}.shell{width:min(100% - 24px,1280px)}.textarea{min-height:320px}}
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="page-head">
<div>
<h1>配置中心</h1>
<p>运行期配置保存在 PostgreSQL不再写回 rules.yaml。代码部署只更新默认模板不覆盖线上策略迭代和系统配置。</p>
</div>
<div class="actions">
<select class="select" id="kindFilter" onchange="loadConfigs()">
<option value="all">全部</option>
<option value="strategy">策略运行时</option>
<option value="system">系统配置</option>
</select>
<button class="btn" onclick="newConfig('strategy')">新增策略</button>
<button class="btn" onclick="newConfig('system')">新增系统</button>
<button class="btn" onclick="loadConfigs()">刷新</button>
</div>
</div>
<div class="hint">建议新闻源、LLM、调度、策略交易属于系统配置复盘 meta、learned_rules、策略覆盖属于策略运行时配置。</div>
<div class="layout">
<section class="panel">
<div class="panel-head"><div class="panel-title">配置列表</div><div class="panel-note" id="countNote">--</div></div>
<div class="config-list" id="configList"><div class="loading">加载中...</div></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">编辑</div><div class="panel-note" id="editNote">选择一项配置</div></div>
<div class="editor">
<div class="field"><label>类型</label><select class="select" id="editKind"><option value="strategy">策略运行时</option><option value="system">系统配置</option></select></div>
<div class="field"><label>Key</label><input class="input" id="editKey" placeholder="例如 event_driven.sources 或 rules_override"></div>
<div class="field"><label>说明</label><input class="input" id="editDesc" placeholder="这项配置的用途"></div>
<div class="field"><label>JSON 配置</label><textarea class="textarea" id="editJson" spellcheck="false">{}</textarea></div>
<div class="actions">
<button class="btn" onclick="saveCurrent()">保存</button>
<button class="btn" onclick="deleteCurrent()">删除</button>
</div>
<div class="msg" id="msg"></div>
</div>
</section>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
var items=[], selected='';
function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
function fmtTime(t){if(!t)return'--';var d=new Date(t);if(isNaN(d.getTime()))return String(t).slice(0,16).replace('T',' ');return (d.getMonth()+1)+'/'+d.getDate()+' '+String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0')}
async function api(url,opt){var r=await fetch(url,opt||{});var d=await r.json().catch(function(){return{}});if(!r.ok)throw new Error(d.detail||d.error||'请求失败');return d}
async function loadConfigs(){document.getElementById('configList').innerHTML='<div class="loading">加载中...</div>';try{var kind=document.getElementById('kindFilter').value;var d=await api('/api/runtime-config?kind='+encodeURIComponent(kind));items=d.items||[];document.getElementById('countNote').textContent=items.length+' 项';renderList();if(items.length&&!selected)selectItem(items[0].kind+'::'+items[0].config_key)}catch(e){document.getElementById('configList').innerHTML='<div class="empty">'+esc(e.message)+'</div>'}}
function renderList(){if(!items.length){document.getElementById('configList').innerHTML='<div class="empty">暂无运行期配置</div>';return}document.getElementById('configList').innerHTML=items.map(function(x){var id=x.kind+'::'+x.config_key;return '<div class="config-row '+(id===selected?'active':'')+'" onclick="selectItem(\''+esc(id)+'\')"><div><span class="badge '+esc(x.kind)+'">'+(x.kind==='system'?'系统':'策略')+'</span></div><div class="key">'+esc(x.config_key)+'</div><div class="meta">'+esc(x.description||'--')+'<br>更新 '+fmtTime(x.updated_at)+' · '+esc(x.source||'system')+'</div></div>'}).join('')}
function selectItem(id){selected=id;var x=items.find(function(i){return i.kind+'::'+i.config_key===id});if(!x)return;document.getElementById('editKind').value=x.kind;document.getElementById('editKey').value=x.config_key;document.getElementById('editDesc').value=x.description||'';document.getElementById('editJson').value=JSON.stringify(x.config||{},null,2);document.getElementById('editNote').textContent=x.kind+' / '+x.config_key;document.getElementById('msg').textContent='';renderList()}
function newConfig(kind){selected='';document.getElementById('editKind').value=kind;document.getElementById('editKey').value='';document.getElementById('editDesc').value='';document.getElementById('editJson').value='{}';document.getElementById('editNote').textContent='新增配置';document.getElementById('msg').textContent='';renderList()}
async function saveCurrent(){var msg=document.getElementById('msg');try{var kind=document.getElementById('editKind').value;var key=document.getElementById('editKey').value.trim();if(!key)throw new Error('请填写 Key');var config=JSON.parse(document.getElementById('editJson').value||'{}');await api('/api/runtime-config/'+encodeURIComponent(kind)+'/'+encodeURIComponent(key),{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({config:config,description:document.getElementById('editDesc').value||''})});msg.className='msg ok';msg.textContent='已保存到 PostgreSQL';selected=kind+'::'+key;await loadConfigs()}catch(e){msg.className='msg err';msg.textContent=e.message}}
async function deleteCurrent(){var kind=document.getElementById('editKind').value;var key=document.getElementById('editKey').value.trim();if(!key)return;if(!confirm('确认删除 '+key+' ?'))return;try{await api('/api/runtime-config/'+encodeURIComponent(kind)+'/'+encodeURIComponent(key),{method:'DELETE'});selected='';newConfig(kind);await loadConfigs()}catch(e){document.getElementById('msg').className='msg err';document.getElementById('msg').textContent=e.message}}
loadConfigs();
</script>
{% endblock %}