<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渠道配置 - Ops-Assistant</title>
<style>
:root{
  --bg:#f5f7fb;
  --text:#222;
  --card:#fff;
  --border:#e5e7eb;
  --muted:#6b7280;
  --accent:#ee5a24;
  --accent-hover:#d63031;
  --header-bg:linear-gradient(135deg,#ff6b6b,#ee5a24);
}
[data-theme="dark"]{
  --bg:#0f172a;
  --text:#e5e7eb;
  --card:#111827;
  --border:#1f2937;
  --muted:#9ca3af;
  --accent:#f97316;
  --accent-hover:#ea580c;
  --header-bg:linear-gradient(135deg,#7c2d12,#ea580c);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
.header { background: var(--header-bg); color: #fff; padding: 18px 20px; position: sticky; top: 0; z-index: 100; box-shadow: 0 2px 10px rgba(0,0,0,.15); display:flex; justify-content:space-between; align-items:center; gap:10px; }
.header a,.header button { color: #fff; text-decoration:none; background: rgba(255,255,255,.2); padding:6px 10px; border-radius:8px; font-size:13px; border:none; cursor:pointer; }
.header button{display:inline-flex;align-items:center;justify-content:center}
.header button svg{width:16px;height:16px;stroke:#fff;fill:none;stroke-width:2}
.header a:hover { background: rgba(255,255,255,.35); }
.wrap { max-width: 760px; margin: 0 auto; padding: 14px; }
.toolbar { display:flex; gap:8px; margin-bottom:10px; flex-wrap:wrap; }
.tip { color:var(--muted); font-size:12px; margin-bottom:10px; }
.card { background:var(--card); border-radius:6px; padding:14px; margin-bottom:10px; border:1px solid var(--border); box-shadow:none; }
.row { display:flex; gap:8px; align-items:center; margin:8px 0; flex-wrap:wrap; }
.row label { width:140px; color:var(--muted); font-size:13px; }
.row input, .row textarea { flex:1; min-width:220px; padding:8px; border:1px solid var(--border); border-radius:6px; font-size:13px; background:var(--card); color:var(--text); }
.row textarea { min-height:74px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.btns { display:flex; gap:8px; margin-top:8px; flex-wrap:wrap; }
button { border:none; border-radius:6px; padding:8px 12px; cursor:pointer; color:#fff; font-size:13px; }
button:disabled { opacity:.65; cursor:not-allowed; }
.btn-apply, .btn-save, .btn-publish, .btn-test, .btn-reload, .btn-enable { background:var(--accent); }
.btn-apply:hover, .btn-save:hover, .btn-publish:hover, .btn-test:hover, .btn-reload:hover, .btn-enable:hover { background:var(--accent-hover); }
.btn-disable { background:#9b2c2c; }
.btn-disable:hover { background:#7f1d1d; }
.btn-ghost { background:transparent; color:var(--muted); border:1px solid var(--border); }
.btn-ghost:hover { background:rgba(0,0,0,.04); }
.badge { display:inline-block; font-size:12px; border-radius:6px; padding:2px 8px; }
.state { display:inline-block; font-size:12px; border-radius:6px; padding:2px 8px; margin-left:6px; }
.ok { background:#dcfce7; color:#166534; }
.error { background:#fee2e2; color:#991b1b; }
.disabled { background:#e5e7eb; color:#374151; }
small { color:var(--muted); }
.hidden{display:none !important;}
.advanced{border-top:1px dashed var(--border); margin-top:8px; padding-top:8px;}

@media(max-width:640px){
  .header { padding: 14px 12px 10px; align-items:flex-start; flex-direction:column; }
  .header > div:last-child { display:flex; gap:8px; }
  .wrap { padding:12px; }
  .toolbar button { flex: 1 1 calc(50% - 4px); }
  .row label { width:100%; }
  .row input, .row textarea { min-width:100%; }
  .btns button { flex: 1 1 calc(50% - 4px); }
}
.theme-hidden{display:none !important;}
</style>
</head>
<body>
<div class="header">
  <div>🦞 渠道配置中心（草稿/发布） · {{.version}}</div>
  <div>
    <button id="themeToggle" class="theme-hidden" title="切换主题" aria-label="切换主题">
      <svg id="themeIcon" viewBox="0 0 24 24" aria-hidden="true">
        <circle cx="12" cy="12" r="5"></circle>
        <line x1="12" y1="2" x2="12" y2="4"></line>
        <line x1="12" y1="20" x2="12" y2="22"></line>
        <line x1="2" y1="12" x2="4" y2="12"></line>
        <line x1="20" y1="12" x2="22" y2="12"></line>
        <line x1="4.2" y1="4.2" x2="5.8" y2="5.8"></line>
        <line x1="18.2" y1="18.2" x2="19.8" y2="19.8"></line>
        <line x1="18.2" y1="5.8" x2="19.8" y2="4.2"></line>
        <line x1="4.2" y1="19.8" x2="5.8" y2="18.2"></line>
      </svg>
    </button>
    <a href="/">返回首页</a>
    <a href="/logout">退出</a>
  </div>
</div>
<div class="wrap">
  <div class="toolbar">
    <button id="btnDisableAll" class="btn-disable" onclick="disableAll()">一键全部关闭</button>
    <button id="btnReload" class="btn-reload" onclick="reloadRuntime()">热加载运行参数</button>
  </div>
  <div class="tip">填写需要的参数即可（每项一个输入框）。高级 JSON 已折叠，默认不需要碰。</div>
  <div id="app"></div>
</div>

<script>
let me=null;
const app = document.getElementById('app');

async function api(url, options = {}) {
  const r = await fetch(url, options);
  const out = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(out.message || ('HTTP ' + r.status));
  return out?.data || {};
}
function esc(s){return String(s||'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
function can(k){return !!(me&&me.effective_capabilities&&me.effective_capabilities[k]);}

function renderError(msg) {
  app.innerHTML = `<div class="card" style="border:1px solid #fecaca;background:#fef2f2;color:#991b1b;">${msg}</div>`;
}

function pretty(objStr) {
  try { return JSON.stringify(JSON.parse(objStr || '{}'), null, 2); } catch { return '{}'; }
}

function statusBadge(status) {
  const s = (status || 'disabled');
  const cls = s === 'ok' ? 'ok' : (s === 'error' ? 'error' : 'disabled');
  return `<span class="badge ${cls}">${s}</span>`;
}

function runtimeState(ch) {
  if (!ch.enabled) return '<span class="state disabled">已关闭</span>';
  if ((ch.status || '').toLowerCase() === 'ok') return '<span class="state ok">运行中</span>';
  if ((ch.status || '').toLowerCase() === 'error') return '<span class="state error">配置异常</span>';
  return '<span class="state disabled">待检测</span>';
}

function parseJSONSafe(text) {
  try { return JSON.parse(text || '{}'); } catch { return null; }
}

function getFieldDefs(platform){
  if(platform==='telegram'){
    return [
      {key:'token', label:'Telegram Bot Token'}
    ];
  }
  if(platform==='qqbot_official'){
    return [
      {key:'appid', label:'QQ Bot AppID'},
      {key:'secret', label:'QQ Bot Secret'}
    ];
  }
  if(platform==='feishu'){
    return [
      {key:'app_id', label:'飞书 AppID'},
      {key:'app_secret', label:'飞书 AppSecret'},
      {key:'verification_token', label:'飞书 VerificationToken（可选）'},
      {key:'encrypt_key', label:'飞书 EncryptKey（可选）'}
    ];
  }
  return [];
}

async function fetchChannels() {
  const out = await api('/api/v1/admin/channels');
  const data = out?.channels;
  if (!Array.isArray(data)) throw new Error('渠道返回格式异常');
  return data;
}

function render(channels) {
  app.innerHTML = channels.map(ch => {
    const draftCfg = pretty(ch.draft_config_json || ch.config_json);
    const draftSec = pretty(ch.draft_secrets || ch.secrets);
    const secObj = parseJSONSafe(draftSec) || {};
    const hasDraft = ch.has_draft ? '<span class="badge" style="background:#fef3c7;color:#92400e">draft</span>' : '';
    const fields = getFieldDefs(ch.platform);
    const fieldsHtml = fields.map(f => {
      let v = secObj[f.key] || '';
    if (String(v).trim() === '***') { v = ''; }
      return `<div class="row"><label>${esc(f.label)}</label><input class="field" data-key="${esc(f.key)}" value="${esc(v)}" placeholder="留空表示不修改"></div>`;
    }).join('');

    return `<div class="card" data-platform="${esc(ch.platform)}">
      <h3>${esc(ch.name || ch.platform)} ${statusBadge(ch.status)} ${runtimeState(ch)} ${hasDraft}</h3>
      <small>平台：${esc(ch.platform)} ｜ 发布：${esc(ch.published_at || '-')} ｜ 最近检测：${esc(ch.last_check_at || '-')}</small>
      <div class="row"><label>启用</label><input type="checkbox" class="enabled" ${ch.enabled ? 'checked' : ''}></div>
      <div class="row"><label>显示名称</label><input class="name" value="${esc(ch.name||'')}"></div>
      ${fieldsHtml}
      <div class="btns">
        <button class="btn-apply" onclick="applyNow('${esc(ch.platform)}')">保存并立即生效</button>
        <button class="btn-save" onclick="saveDraft('${esc(ch.platform)}')">保存草稿</button>
        <button class="btn-publish" onclick="publishDraft('${esc(ch.platform)}')">发布草稿</button>
        <button class="btn-test" onclick="testConn('${esc(ch.platform)}')">测试连接</button>
        ${ch.enabled ? `<button class="btn-disable" onclick="toggleChannel('${esc(ch.platform)}', false)">关闭通道</button>` : `<button class="btn-enable" onclick="toggleChannel('${esc(ch.platform)}', true)">开启通道</button>`}
        <button class="btn-ghost" onclick="toggleAdvanced('${esc(ch.platform)}')">高级 JSON</button>
      </div>
      <div class="advanced hidden">
        <div class="row"><label>配置 JSON</label><textarea class="config">${draftCfg}</textarea></div>
        <div class="row"><label>密钥 JSON</label><textarea class="secrets">${draftSec}</textarea></div>
      </div>
      <small class="msg"></small>
    </div>`;
  }).join('');
}

function collectChannelForm(platform) {
  const card = document.querySelector(`[data-platform="${platform}"]`);
  const name = card.querySelector('.name').value.trim();
  const enabled = card.querySelector('.enabled').checked;
  const cfgText = card.querySelector('.config') ? card.querySelector('.config').value : '{}';
  const secText = card.querySelector('.secrets') ? card.querySelector('.secrets').value : '{}';
  const msg = card.querySelector('.msg');

  const config = parseJSONSafe(cfgText) || {};
  const secrets = parseJSONSafe(secText) || {};

  if (config === null || secrets === null) {
    msg.textContent = 'JSON 格式错误，请检查高级 JSON';
    return null;
  }

  // 覆盖结构化字段，但保留未知字段
  let hasSecretChange = false;
  card.querySelectorAll('.field').forEach(input => {
    const key = input.getAttribute('data-key');
    const val = (input.value || '').trim();
    if (val !== '') {
      secrets[key] = val;
      hasSecretChange = true;
    }
  });

  const payload = { name, enabled, config };
  if (hasSecretChange) payload.secrets = secrets;

  return { card, msg, payload };
}

function setCardBusy(card, busy) {
  if (!card) return;
  card.querySelectorAll('button').forEach(btn => { btn.disabled = busy; });
}

async function applyNow(platform) {
  const f = collectChannelForm(platform);
  if (!f) return;
  const { card, msg, payload } = f;
  const applyBtn = card.querySelector('.btn-apply');
  const oldText = applyBtn ? applyBtn.textContent : '';

  setCardBusy(card, true);
  if (applyBtn) applyBtn.textContent = '生效中...';

  try {
    msg.textContent = '保存并生效中...';
    await api('/api/v1/admin/channels/' + platform + '/apply', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
    msg.textContent = '已生效';
    await reload();
  } catch (e) {
    msg.textContent = '失败：' + (e && e.message ? e.message : e);
  } finally {
    setCardBusy(card, false);
    if (applyBtn) applyBtn.textContent = oldText || '保存并立即生效';
  }
}

async function saveDraft(platform) {
  const f = collectChannelForm(platform);
  if (!f) return;
  const { msg, payload } = f;

  try {
    await api('/api/v1/admin/channels/' + platform, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
    msg.textContent = '草稿已保存';
    await reload();
  } catch (e) {
    msg.textContent = '保存失败：' + (e && e.message ? e.message : e);
  }
}

async function publishDraft(platform) {
  const msg = msgOf(platform);
  try {
    await api('/api/v1/admin/channels/' + platform + '/publish', { method: 'POST' });
    msg.textContent = '发布成功，建议点“热加载运行参数”';
    await reload();
  } catch (e) {
    msg.textContent = '发布失败：' + (e && e.message ? e.message : e);
  }
}

async function testConn(platform) {
  const msg = msgOf(platform);
  try {
    msg.textContent = '正在测试...';
    const out = await api('/api/v1/admin/channels/' + platform + '/test', { method: 'POST' });
    msg.textContent = `测试结果：${out.status || 'unknown'} ${out.detail ? ' / ' + out.detail : ''}`;
    await reload();
  } catch (e) {
    msg.textContent = '测试失败：' + (e && e.message ? e.message : e);
  }
}

async function toggleChannel(platform, enable) {
  const msg = msgOf(platform);
  try {
    msg.textContent = enable ? '正在开启...' : '正在关闭...';
    await api('/api/v1/admin/channels/' + platform + (enable ? '/enable' : '/disable'), { method: 'POST' });
    msg.textContent = enable ? '已开启（请点热加载生效）' : '已关闭（请点热加载生效）';
    await reload();
  } catch (e) {
    msg.textContent = (enable ? '开启失败：' : '关闭失败：') + (e && e.message ? e.message : e);
  }
}

async function reloadRuntime() {
  try {
    const out = await api('/api/v1/admin/channels/reload', { method: 'POST' });
    alert('热加载成功：' + (out.detail || 'ok'));
    await reload();
  } catch (e) {
    alert('热加载失败：' + (e && e.message ? e.message : e));
  }
}

async function disableAll() {
  if (!confirm('确认要关闭所有通道吗？')) return;
  try {
    const out = await api('/api/v1/admin/channels/disable-all', { method: 'POST' });
    alert('已关闭通道数：' + (out.affected || 0) + '，请点热加载生效。');
    await reload();
  } catch (e) {
    alert('批量关闭失败：' + (e && e.message ? e.message : e));
  }
}

function msgOf(platform) {
  return document.querySelector(`[data-platform="${platform}"] .msg`);
}

function toggleAdvanced(platform){
  const card = document.querySelector(`[data-platform="${platform}"]`);
  if(!card) return;
  const adv = card.querySelector('.advanced');
  if(adv) adv.classList.toggle('hidden');
}

async function loadMe(){
  const r=await fetch('/api/v1/me');
  const out=await r.json().catch(()=>({}));
  if(!r.ok) throw new Error(out.message||'读取用户失败');
  me=out?.data||{};
}

function initPermissionUI(){
  document.getElementById('btnReload').classList.toggle('hidden',!can('can_edit_channels'));
  document.getElementById('btnDisableAll').classList.toggle('hidden',!can('can_edit_channels'));
}

function applyTheme(theme){
  if(theme==='dark'){
    document.documentElement.setAttribute('data-theme','dark');
  }else{
    document.documentElement.removeAttribute('data-theme');
  }
  localStorage.setItem('theme',theme);
}
function toggleTheme(){
  const cur=localStorage.getItem('theme')||'dark';
  applyTheme(cur==='light'?'dark':'light');
}
function initTheme(){
  const saved=localStorage.getItem('theme')||'dark';
  applyTheme(saved);
  const btn=document.getElementById('themeToggle');
  if(btn){ btn.addEventListener('click',toggleTheme); }
}

async function reload() {
  try {
    const channels = await fetchChannels();
    render(channels);
  } catch (e) {
    console.error(e);
    renderError('页面加载失败：' + (e && e.message ? e.message : e));
  }
}

window.addEventListener('error', (e) => {
  renderError('前端脚本异常：' + (e && e.message ? e.message : 'unknown'));
});

window.addEventListener('unhandledrejection', (e) => {
  const msg = e && e.reason && e.reason.message ? e.reason.message : String(e.reason || 'unknown');
  renderError('前端请求异常：' + msg);
});

(async function(){
  try{ initTheme(); await loadMe(); initPermissionUI(); await reload(); }
  catch(e){ renderError('初始化失败：'+(e.message||e)); }
})();
</script>
</body>
</html>
