This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "pigway-frame-flow",
|
||||
"name": "PIGWay Frame Flow",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"protocol_version": 1,
|
||||
"runtime": "python3",
|
||||
"entrypoint": "worker.py",
|
||||
@@ -29,6 +29,14 @@
|
||||
"zh": "Frame Flow API 令牌",
|
||||
"en": "Frame Flow API token"
|
||||
}
|
||||
},
|
||||
"management_url": {
|
||||
"type": "string",
|
||||
"format": "http-origin",
|
||||
"label": {
|
||||
"zh": "管理入口地址(留空使用服务地址)",
|
||||
"en": "Management URL (blank uses service URL)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
|
||||
+55
-2
@@ -1,6 +1,17 @@
|
||||
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 { $, 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 `<a class="docker-icon" style="margin-left: -7px" href="${esc(url.href)}" target="_blank" rel="noopener noreferrer" title="${label}" aria-label="${label}"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 4h6v6M20 4l-10 10M11 4H4v16h16v-7"/></svg></a>`;
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
const metricValue = metric => {
|
||||
if (metric.unit === 'percent') return `${metric.value}%`;
|
||||
@@ -46,5 +57,47 @@ export function hostCard(entry) {
|
||||
});
|
||||
}).join('');
|
||||
const frames = resources.filter(resource => resource.type === 'device');
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(entry.host.name || 'Frame Flow')}</h2></div><div class="device-details">${frames.map(frame => `<span>${esc(frame.name || text('画框', 'Frame'))}</span>`).join('')}</div></div><span class="badge semantic-status" data-semantic="${current ? 'online' : 'unknown'}">${current ? text('在线', 'Online') : text('状态未确认', 'Unconfirmed')}</span></div>${!current ? `<p class="muted">${text('以下为上次采集数据', 'Showing the last collected data')}</p>` : ''}<div class="host-grid">${cards}</div>${(snapshot.alerts || []).length || !current || snapshot.alerts_complete !== true ? `<details class="group" data-overview-details="alerts"><summary class="smart-summary"><strong class="smart-summary-title">${text('已读取范围内的告警', 'Alerts within monitored scope')}</strong>${summaryStatus(scopeAlertHealth(snapshot.alerts, snapshot.alerts_complete, current), state.LANG)}</summary><div style="margin-top:12px">${alertRows || (current && snapshot.alerts_complete ? text('无告警', 'No alerts') : text('状态未确认', 'Unconfirmed'))}</div></details>` : ""}</article>`;
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(entry.host.name || 'Frame Flow')}</h2>${managementLink(entry.host)}</div><div class="device-details">${frames.map(frame => `<span>${esc(frame.name || text('画框', 'Frame'))}</span>`).join('')}</div></div><span class="badge semantic-status" data-semantic="${current ? 'online' : 'unknown'}">${current ? text('在线', 'Online') : text('状态未确认', 'Unconfirmed')}</span></div>${!current ? `<p class="muted">${text('以下为上次采集数据', 'Showing the last collected data')}</p>` : ''}<div class="host-grid">${cards}</div>${(snapshot.alerts || []).length || !current || snapshot.alerts_complete !== true ? `<details class="group" data-overview-details="alerts"><summary class="smart-summary"><strong class="smart-summary-title">${text('已读取范围内的告警', 'Alerts within monitored scope')}</strong>${summaryStatus(scopeAlertHealth(snapshot.alerts, snapshot.alerts_complete, current), state.LANG)}</summary><div style="margin-top:12px">${alertRows || (current && snapshot.alerts_complete ? text('无告警', 'No alerts') : text('状态未确认', 'Unconfirmed'))}</div></details>` : ""}</article>`;
|
||||
}
|
||||
|
||||
|
||||
export function renderConfig() {
|
||||
const host = state.fleetHosts.find(h => h.id === state.currentConfigHost);
|
||||
const connection = host.connection || {};
|
||||
$('mainSavebar').classList.add('hidden');
|
||||
$('configMenu').innerHTML = `<button data-config-section="frame-flow" class="active">${text('Frame Flow 接入', 'Frame Flow connection')}</button>`;
|
||||
$('configRows').innerHTML = `<section class="config-page active" data-config-page="frame-flow"><h2>${text('Frame Flow 接入', 'Frame Flow connection')}</h2><p class="hint">${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.')}</p><div class="fields">${[
|
||||
['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]) => `<div class="field"><label>${label}<input data-frame-flow-connection="${key}" type="${key === 'token' ? 'password' : 'url'}" value="${esc(value)}" autocomplete="${key === 'token' ? 'new-password' : 'off'}"></label></div>`).join('')}</div><div class="action-row"><button id="saveFrameFlowConnection">${text('保存接入配置', 'Save connection')}</button><span id="frameFlowConnectionMessage" role="status"></span></div></section>`;
|
||||
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; }
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user