66 lines
11 KiB
HTML
66 lines
11 KiB
HTML
{% 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}.btn:disabled,.input:disabled,.textarea:disabled,.select:disabled{opacity:.55;cursor:not-allowed;background:var(--surface)}.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;margin-top:7px}.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);margin:0 4px 4px 0}.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)}.badge.locked{color:var(--orange);background:rgba(255,174,0,.1);border-color:rgba(255,174,0,.22)}.badge.editable{color:var(--blue);background:rgba(66,98,255,.07);border-color:rgba(66,98,255,.16)}.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}.guard{padding:10px 12px;border:1px solid rgba(255,174,0,.2);background:rgba(255,174,0,.08);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.55;margin-bottom:12px}@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,但这里只展示适合在线调参的项目。密钥、数据库、SMTP、交易所底层连接和启动管理员只通过环境变量或专用页面管理。</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="loadConfigs()">刷新</button>
|
||
</div>
|
||
</div>
|
||
<div class="hint">配置边界:这里适合调策略交易、通知开关、事件/舆情、监控和调度参数。API key、webhook 真实地址、SMTP 密码、DATABASE_URL、交易所 endpoint 等只放环境变量,避免线上配置漂移和误暴露。</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="guard" id="guardNote">选择一项配置后会显示它的管理边界。</div>
|
||
<div class="field"><label>JSON 配置</label><textarea class="textarea" id="editJson" spellcheck="false">{}</textarea></div>
|
||
<div class="actions">
|
||
<button class="btn" id="saveBtn" onclick="saveCurrent()">保存</button>
|
||
<button class="btn" id="deleteBtn" 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 {'&':'&','<':'<','>':'>','"':'"',"'":'''}[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 currentItem(){return items.find(function(i){return i.kind+'::'+i.config_key===selected})||null}
|
||
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;var perm=x.editable===false?'只读':'可在线调整';var permClass=x.editable===false?'locked':'editable';return '<div class="config-row '+(id===selected?'active':'')+'" onclick="selectItem(\''+esc(id)+'\')"><div><span class="badge '+esc(x.kind)+'">'+(x.kind==='system'?'系统':'策略')+'</span><span class="badge '+permClass+'">'+perm+'</span></div><div class="key">'+esc(x.config_key)+'</div><div class="meta">'+esc(x.description||'--')+'<br>'+esc(x.category||'运行配置')+' · '+esc(x.source_of_truth||'配置中心')+' · 更新 '+fmtTime(x.updated_at)+'</div></div>'}).join('')}
|
||
function applyEditMode(x){var editable=!x||x.editable!==false;var deletable=!!(x&&x.delete_allowed);document.getElementById('editKind').disabled=!!x;document.getElementById('editKey').disabled=!!x;document.getElementById('editDesc').disabled=!editable;document.getElementById('editJson').disabled=!editable;document.getElementById('saveBtn').disabled=!editable;document.getElementById('deleteBtn').disabled=!deletable;var note='新建策略运行时配置。系统配置必须先在后端登记管理边界,不能从页面随意新增。';if(x){note=(x.editable===false?'只读配置。':'可在线调整。')+'来源:'+(x.source_of_truth||'配置中心')+'。'+(x.warning?' '+x.warning:'')}document.getElementById('guardNote').textContent=note}
|
||
function selectItem(id){selected=id;var x=currentItem();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='';applyEditMode(x);renderList()}
|
||
function newConfig(kind){if(kind==='system'){document.getElementById('msg').className='msg err';document.getElementById('msg').textContent='系统配置不能从页面新增,请先在后端登记配置边界。';return}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='';applyEditMode(null);renderList()}
|
||
async function saveCurrent(){var msg=document.getElementById('msg');try{var x=currentItem();if(x&&x.editable===false)throw new Error('该配置不可在配置中心编辑');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 x=currentItem();if(x&&x.delete_allowed===false){document.getElementById('msg').className='msg err';document.getElementById('msg').textContent='该配置不允许从配置中心删除';return}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('strategy');await loadConfigs()}catch(e){document.getElementById('msg').className='msg err';document.getElementById('msg').textContent=e.message}}
|
||
loadConfigs();
|
||
</script>
|
||
{% endblock %}
|