import { scopeAlertHealth, summaryStatus } from '/assets/js/overview-health.js';
import { state } from '/assets/js/state.js';
import { $, esc, pluginText as text } from '/assets/js/shared.js';
import { get } from '/assets/js/navigation.js';
function managementLink(host) {
try {
const url = new URL(host.connection?.management_url || host.connection?.url || host.url);
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return '';
const label = text('打开 Frame Flow 管理', 'Open Frame Flow management');
return ` `;
} catch { return ''; }
}
const metricValue = metric => {
if (metric.unit === 'percent') return `${metric.value}%`;
if (metric.unit === 'count') return `${metric.value} ${text('个', 'items')}`;
const values = {
service_ready: [text('未就绪', 'Not ready'), text('就绪', 'Ready')],
device_online: [text('离线', 'Offline'), text('在线', 'Online')],
job_enabled: [text('停用', 'Disabled'), text('启用', 'Enabled')],
last_run_success: [text('失败或待确认', 'Failed or unconfirmed'), text('成功', 'Succeeded')],
};
return values[metric.key]?.[metric.value] ?? String(metric.value);
};
export function hostCard(entry) {
const snapshot = entry.snapshot || {};
const resources = snapshot.resources || [];
const current = entry.dataCurrent === true;
const label = item => item.label?.[state.LANG] || item.key || item.id;
const name = id => resources.find(r => r.id === id)?.name || id;
const alertRows = (snapshot.alerts || []).map(alert => {
const separator = alert.id.indexOf(':');
const kind = alert.id.split(':')[0];
const resource = separator < 0 ? '' : `${kind.startsWith('JOB_') ? 'job' : 'device'}:${alert.id.slice(separator + 1)}`;
return `
${esc(entry.host.name || 'Frame Flow')} ${managementLink(entry.host)}${frames.map(frame => `${esc(frame.name || text('画框', 'Frame'))} `).join('')}
${current ? text('在线', 'Online') : text('状态未确认', 'Unconfirmed')} ${!current ? `${text('以下为上次采集数据', 'Showing the last collected data')}
` : ''}${cards}
${(snapshot.alerts || []).length || !current || snapshot.alerts_complete !== true ? `${text('已读取范围内的告警', 'Alerts within monitored scope')} ${summaryStatus(scopeAlertHealth(snapshot.alerts, snapshot.alerts_complete, current), state.LANG)}${alertRows || (current && snapshot.alerts_complete ? text('无告警', 'No alerts') : text('状态未确认', 'Unconfirmed'))}
` : ""} `;
}
export function renderConfig() {
const host = state.fleetHosts.find(h => h.id === state.currentConfigHost);
const connection = host.connection || {};
$('mainSavebar').classList.add('hidden');
$('configMenu').innerHTML = `${text('Frame Flow 接入', 'Frame Flow connection')} ${text('配置保存在 Console;管理入口留空时,首页链接使用 Frame Flow 服务地址。API 令牌留空保留现有值。', 'Settings are stored in Console. Leave the management URL blank to use the Frame Flow service URL on the overview. A blank API token keeps the existing value.')}
${[
['url', text('Frame Flow 地址', 'Frame Flow URL'), connection.url || host.url || ''],
['token', text('API 令牌(留空保留)', 'API token (blank keeps existing)'), ''],
['management_url', text('管理入口地址(留空使用服务地址)', 'Management URL (blank uses service URL)'), connection.management_url || ''],
].map(([key, label, value]) => `
${label}
`).join('')}
${text('保存接入配置', 'Save connection')}
`;
const page = $('configRows').querySelector('[data-config-page="frame-flow"]');
$('configMenu').onclick = event => {
if (!event.target.closest('[data-config-section="frame-flow"]')) return;
$('configMenu').querySelectorAll('button').forEach(b => b.classList.toggle('active', b.dataset.configSection === 'frame-flow'));
$('configRows').querySelectorAll('[data-config-page]').forEach(p => p.classList.toggle('active', p === page));
$('mainSavebar').classList.add('hidden');
};
$('saveFrameFlowConnection').onclick = async () => {
const button = $('saveFrameFlowConnection'), message = $('frameFlowConnectionMessage');
const values = Object.fromEntries([...page.querySelectorAll('[data-frame-flow-connection]')].map(input => [input.dataset.frameFlowConnection, input.value.trim()]));
for (const [key, value] of Object.entries(values)) {
if (key === 'token' || (key === 'management_url' && !value)) continue;
try {
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.pathname !== '/' || url.search || url.hash) throw Error();
} catch {
message.textContent = text('请使用不含路径或账号密码的 HTTP(S) 根地址', 'Use an HTTP(S) origin without paths or credentials');
message.className = 'error'; return;
}
}
button.disabled = true;
try {
const result = await get('/api/v1/hosts', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ operation: 'save', id: host.id, adapter_id: host.adapter_id, connection: values }) });
state.fleetHosts = result.hosts;
page.querySelector('[data-frame-flow-connection="token"]').value = '';
message.textContent = text('已保存', 'Saved'); message.className = 'success';
} catch (error) { message.textContent = error.message; message.className = 'error'; }
finally { button.disabled = false; }
};
}