Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
4. 在 **环境变量 (Environment Variables)** 中添加以下配置:
- `SECRET_ID`: 您的腾讯云 SecretId
- `SECRET_KEY`: 您的腾讯云 SecretKey
- `SITE_NAME`: (可选) 自定义大屏标题,默认为 "AcoFork 的 EdgeOne 监控大屏"
- `SITE_ICON`: (可选) 自定义网页图标,默认为 "https://q2.qlogo.cn/headimg_dl?dst_uin=2726730791&spec=0"
- `SITE_NAME`: 大屏标题,固定为 "茶茶吖 的 EdgeOne 监控大屏"
- `SITE_ICON`: 网页图标,固定为 "https://blog.ccya.top/logo.svg"
5. 部署项目。

### 方式二:本地运行 / Node.js 环境
Expand Down
182 changes: 119 additions & 63 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="https://q2.qlogo.cn/headimg_dl?dst_uin=2726730791&spec=0">
<link rel="icon" href="https://blog.ccya.top/logo.svg">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AcoFork 的 EdgeOne 监控大屏</title>
<title>茶茶吖 的 EdgeOne 监控大屏</title>
<!-- Tailwind CSS for Shadcn/UI style -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
Expand Down Expand Up @@ -116,7 +116,7 @@
<!-- Header -->
<div class="flex items-center justify-between">
<div>
<h1 id="page-header" class="text-3xl font-bold tracking-tight text-slate-900">AcoFork 的 EdgeOne 监控大屏</h1>
<h1 id="page-header" class="text-3xl font-bold tracking-tight text-slate-900">茶茶吖 的 EdgeOne 监控大屏</h1>
<p class="text-muted-foreground mt-1">EdgeOne 站点流量与请求量分析</p>
</div>
</div>
Expand Down Expand Up @@ -171,10 +171,16 @@ <h1 id="page-header" class="text-3xl font-bold tracking-tight text-slate-900">Ac
</div>
<div class="flex items-center space-x-2">
<label for="zoneId" class="text-sm font-medium">选择站点:</label>
<select id="zoneId" onchange="refreshData()" class="h-9 w-[240px] rounded-md border border-slate-200 bg-white px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-900">
<select id="zoneId" onchange="handleZoneChange()" class="h-9 w-[240px] rounded-md border border-slate-200 bg-white px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-900">
<option value="*">加载中...</option>
</select>
</div>
<div class="flex items-center space-x-2">
<label for="domainSelect" class="text-sm font-medium">选择域名:</label>
<select id="domainSelect" onchange="refreshData()" class="h-9 w-[240px] rounded-md border border-slate-200 bg-white px-3 py-1 text-sm disabled:opacity-50" disabled>
<option value="*">全部域名</option>
</select>
</div>
<!-- <button onclick="refreshData()" class="h-9 px-4 py-2 bg-slate-900 text-white hover:bg-slate-900/90 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 shadow">
刷新
</button> -->
Expand Down Expand Up @@ -1203,6 +1209,8 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行

const interval = document.getElementById('interval').value;
const zoneId = document.getElementById('zoneId').value.trim();
const domainSelect = document.getElementById('domainSelect');
const domain = domainSelect ? domainSelect.value.trim() : '*';

let url = `/api/traffic?metric=${metric}&startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`;
if (interval && interval !== 'auto') {
Expand All @@ -1211,6 +1219,9 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行
if (zoneId) {
url += `&zoneId=${encodeURIComponent(zoneId)}`;
}
if (zoneId && zoneId !== '*' && domain && domain !== '*') {
url += `&domain=${encodeURIComponent(domain)}`;
}

const response = await fetch(url);
const result = await response.json();
Expand Down Expand Up @@ -1399,16 +1410,57 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行
select.value = "*";
}

await fetchDomains(select.value);
} catch (err) {
console.error("Error fetching zones:", err);
const select = document.getElementById('zoneId');
// If error, ensure we have at least the default * option
if (select.options.length === 0 || select.options[0].value !== '*') {
select.innerHTML = '<option value="*">获取站点失败 (默认为全部)</option>';
}
await fetchDomains("*");
}
}

async function fetchDomains(zoneId) {
const select = document.getElementById('domainSelect');
if (!select) return;

if (!zoneId || zoneId === '*') {
select.innerHTML = '<option value="*">全部域名</option>';
select.disabled = true;
return;
}

try {
select.disabled = false;
select.innerHTML = '<option value="*">加载中...</option>';

const response = await fetch(`/api/domains?zoneId=${encodeURIComponent(zoneId)}`);
const result = await response.json();

select.innerHTML = '<option value="*">全部域名</option>';
if (result.AccelerationDomains && result.AccelerationDomains.length > 0) {
result.AccelerationDomains.forEach(domain => {
const option = document.createElement('option');
option.value = domain.DomainName;
option.text = domain.DomainName;
select.appendChild(option);
});
}
} catch (err) {
console.error("Error fetching domains:", err);
select.innerHTML = '<option value="*">获取域名失败 (默认为全部)</option>';
select.disabled = true;
}
}

async function handleZoneChange() {
const zoneId = document.getElementById('zoneId').value.trim();
await fetchDomains(zoneId);
refreshData();
}

////ENTRANCE////
async function initDashboard() {
// Initialize charts
Expand Down Expand Up @@ -2348,35 +2400,29 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行
}

//通用处理
const renderTopChart = (results, chartInstance, metricName, mapObject = null) => {
const renderTopChart = (results, chartInstance, metricName, mapObject = null, config = {}) => {
const { keyFormatter, tooltipValueFormatter, axisLabelFormatter } = config;
const data = results[metricName];

if (!data || data.type !== 'top' || !data.data) return;

const sortedData = [...data.data].sort((a, b) => b.Value - a.Value).slice(0, 10);

let unit = '';
let divisor = 1;
let label = metricLabels[metricName] || '';

if (metricName.includes('outFlux')) {
const maxVal = sortedData.length > 0 ? sortedData[0].Value : 0;
const best = getBestUnit(maxVal, 'bytes');
unit = best.unit;
divisor = best.divisor;
} else {
const maxVal = sortedData.length > 0 ? sortedData[0].Value : 0;
const best = getBestCountUnit(maxVal);
unit = best.unit;
divisor = best.divisor;
}

const yAxisData = sortedData.map(item => mapObject?.[item.Key] || item.Key).reverse(); //问题点:没对“字段不存在”的短横杠做判断
const seriesData = sortedData.map(item => {
const val = item.Value / divisor;
return (divisor === 1) ? val : val.toFixed(2);
const unitInfo = metricName.includes('outFlux')
? getBestUnit(sortedData[0]?.Value || 0, 'bytes')
: getBestCountUnit(sortedData[0]?.Value || 0);

const yAxisData = sortedData.map(item => {
const mapped = mapObject?.[item.Key] || item.Key;
return keyFormatter ? keyFormatter(mapped) : mapped;
}).reverse();

const rawSeriesData = sortedData.map(item => item.Value).reverse();
const seriesData = rawSeriesData.map(val => {
const converted = val / unitInfo.divisor;
return unitInfo.divisor === 1 ? converted : converted.toFixed(2);
});

let color = '#3b82f6';
const lowerName = metricName.toLowerCase();
if (lowerName.includes('country')) color = '#3b82f6';
Expand All @@ -2391,41 +2437,32 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params) => {
const param = params[0];
let name = param.name;
if (name.length > 100) name = name.substring(0, 100) + '...';

const rawVal = sortedData[sortedData.length - 1 - param.dataIndex].Value;

let formattedVal = '';
if (metricName.includes('outFlux')) {
formattedVal = formatBytes(rawVal);
} else {
const fVal = formatCount(rawVal);
// Append '次' if not present in unit (formatCount returns unit, but we might want explicit '次')
// formatCount returns "1.23 万". "1.23 万次" sounds good.
// "500" -> "500 次".
formattedVal = fVal + (fVal.includes('千') || fVal.includes('万') || fVal.includes('亿') ? '次' : ' 次');
}

return `${name}<br/>${param.marker}${label}: ${formattedVal}`;
const param = params[0];
const rawVal = rawSeriesData[param.dataIndex];
let formattedVal;
if (tooltipValueFormatter) {
formattedVal = tooltipValueFormatter(rawVal, metricName);
} else if (metricName.includes('outFlux')) {
formattedVal = formatBytes(rawVal);
} else {
const fVal = formatCount(rawVal);
formattedVal = fVal + (fVal.includes('千') || fVal.includes('万') || fVal.includes('亿') ? '次' : ' 次');
}
return `${param.name}<br/>${param.marker}${metricLabels[metricName] || ''}: ${formattedVal}`;
}
},
grid: { left: '3%', right: '10%', bottom: '3%', containLabel: true },
xAxis: { type: 'value', name: unit },
yAxis: {
type: 'category',
xAxis: { type: 'value', name: unitInfo.unit },
yAxis: {
type: 'category',
data: yAxisData,
axisLabel: {
formatter: function (value) {
if (value.length > 20) return value.substring(0, 20) + '...';
return value;
}
formatter: axisLabelFormatter || ((value) => value.length > 20 ? value.substring(0, 20) + '...' : value)
}
},
series: [
{
name: label,
name: metricLabels[metricName] || '',
type: 'bar',
data: seriesData,
itemStyle: { color: color },
Expand Down Expand Up @@ -2497,35 +2534,54 @@ <h3 class="font-semibold leading-none tracking-tight">User Agent 请求数排行
function updateTopAnalysisSection(results) {
updateTopMapChart(results);

// Flux Charts
const truncate20 = (val) => val.length > 20 ? val.substring(0, 20) + '...' : val;
const truncate50 = (val) => val.length > 50 ? val.substring(0, 50) + '...' : val;
const refererFormatter = (key) => {
const cleaned = (key || '').replace(/`/g, '').trim();
return cleaned && cleaned !== '-' ? cleaned : '字段不存在';
};
const refererAxisFormatter = (val) => truncate50(refererFormatter(val));
const refererTooltipFormatter = (raw) => {
const formatted = formatCount(raw);
return formatted + (formatted.includes('千') || formatted.includes('万') || formatted.includes('亿') ? '次' : ' 次');
};

// Flux Charts
renderTopChart(results, chartTopCountry, 'l7Flow_outFlux_country', countryMap);
renderTopChart(results, chartTopProvince, 'l7Flow_outFlux_province', provinceMap);
renderTopChart(results, chartTopStatusCode, 'l7Flow_outFlux_statusCode');
renderTopChart(results, chartTopDomain, 'l7Flow_outFlux_domain');
renderTopChart(results, chartTopUrl, 'l7Flow_outFlux_url');
renderTopChart(results, chartTopStatusCode, 'l7Flow_outFlux_statusCode', null, { axisLabelFormatter: truncate20 });
renderTopChart(results, chartTopDomain, 'l7Flow_outFlux_domain', null, { axisLabelFormatter: truncate50 });
renderTopChart(results, chartTopUrl, 'l7Flow_outFlux_url', null, { axisLabelFormatter: truncate50 });
renderTopChart(results, chartTopResourceType, 'l7Flow_outFlux_resourceType');
renderTopChart(results, chartTopSip, 'l7Flow_outFlux_sip');
//renderTopChart(results, chartTopReferer, 'l7Flow_outFlux_referers'); //以前方案
updateTopRefererChart(results); //降级处理,包含“字段不存在”处理
renderTopChart(results, chartTopReferer, 'l7Flow_outFlux_referers', null, {
keyFormatter: refererFormatter,
axisLabelFormatter: refererAxisFormatter,
tooltipValueFormatter: (raw) => formatBytes(raw)
});
renderTopChart(results, chartTopUaDevice, 'l7Flow_outFlux_ua_device');
renderTopChart(results, chartTopUaBrowser, 'l7Flow_outFlux_ua_browser');
renderTopChart(results, chartTopUaOs, 'l7Flow_outFlux_ua_os');
renderTopChart(results, chartTopUa, 'l7Flow_outFlux_ua');
renderTopChart(results, chartTopUa, 'l7Flow_outFlux_ua', null, { axisLabelFormatter: truncate50 });

// Request Charts
renderTopChart(results, chartTopRequestCountry, 'l7Flow_request_country', countryMap);
renderTopChart(results, chartTopRequestProvince, 'l7Flow_request_province', provinceMap);
renderTopChart(results, chartTopRequestStatusCode, 'l7Flow_request_statusCode');
renderTopChart(results, chartTopRequestDomain, 'l7Flow_request_domain');
renderTopChart(results, chartTopRequestUrl, 'l7Flow_request_url');
renderTopChart(results, chartTopRequestStatusCode, 'l7Flow_request_statusCode', null, { axisLabelFormatter: truncate20 });
renderTopChart(results, chartTopRequestDomain, 'l7Flow_request_domain', null, { axisLabelFormatter: truncate50 });
renderTopChart(results, chartTopRequestUrl, 'l7Flow_request_url', null, { axisLabelFormatter: truncate50 });
renderTopChart(results, chartTopRequestResourceType, 'l7Flow_request_resourceType');
renderTopChart(results, chartTopRequestSip, 'l7Flow_request_sip');
//renderTopChart(results, chartTopRequestReferer, 'l7Flow_request_referers');
updateTopRequestRefererChart(results); //降级方案
renderTopChart(results, chartTopRequestReferer, 'l7Flow_request_referers', null, {
keyFormatter: refererFormatter,
axisLabelFormatter: refererAxisFormatter,
tooltipValueFormatter: refererTooltipFormatter
});
renderTopChart(results, chartTopRequestUaDevice, 'l7Flow_request_ua_device');
renderTopChart(results, chartTopRequestUaBrowser, 'l7Flow_request_ua_browser');
renderTopChart(results, chartTopRequestUaOs, 'l7Flow_request_ua_os');
renderTopChart(results, chartTopRequestUa, 'l7Flow_request_ua');
renderTopChart(results, chartTopRequestUa, 'l7Flow_request_ua', null, { axisLabelFormatter: truncate50 });
}


Expand Down
Loading