优化一点东西 懒得写了
This commit is contained in:
@@ -471,14 +471,25 @@
|
|||||||
dom.detailUptime.textContent = `${days}天 ${hours}小时 ${mins}分`;
|
dom.detailUptime.textContent = `${days}天 ${hours}小时 ${mins}分`;
|
||||||
|
|
||||||
// Define metrics to show
|
// Define metrics to show
|
||||||
|
const cpuValueHtml = `
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 2px;">
|
||||||
|
<span style="font-weight: 700;">${formatPercent(data.cpuBusy)}</span>
|
||||||
|
<div style="font-size: 0.65rem; color: var(--text-secondary); display: flex; gap: 6px; font-weight: normal;">
|
||||||
|
<span>Sys: ${data.cpuSystem.toFixed(1)}%</span>
|
||||||
|
<span>User: ${data.cpuUser.toFixed(1)}%</span>
|
||||||
|
<span>Wait: ${data.cpuIowait.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
const metrics = [
|
const metrics = [
|
||||||
{ key: 'cpuBusy', label: 'CPU 忙碌率 (Busy)', value: formatPercent(data.cpuBusy) },
|
{ key: 'cpuBusy', label: 'CPU 使用率 (Busy Breakdown)', value: cpuValueHtml },
|
||||||
{ key: 'sysLoad', label: '系统负载 (Load)', value: data.sysLoad.toFixed(1) + '%' },
|
{ key: 'sysLoad', label: '系统负载 (Load)', value: data.sysLoad.toFixed(1) + '%' },
|
||||||
{ key: 'memUsedPct', label: '内存使用率 (RAM)', value: formatPercent(data.memUsedPct) },
|
{ key: 'memUsedPct', label: '内存使用率 (RAM)', value: formatPercent(data.memUsedPct) },
|
||||||
{ key: 'swapUsedPct', label: 'SWAP 使用率', value: formatPercent(data.swapUsedPct) },
|
{ key: 'swapUsedPct', label: 'SWAP 使用率', value: formatPercent(data.swapUsedPct) },
|
||||||
{ key: 'rootFsUsedPct', label: '根分区使用率 (/)', value: formatPercent(data.rootFsUsedPct) },
|
{ key: 'rootFsUsedPct', label: '根分区使用率 (/)', value: formatPercent(data.rootFsUsedPct) },
|
||||||
{ key: 'netRx', label: '网络接收速率 (RX)', value: formatBandwidth(data.netRx) },
|
{ key: 'netRx', label: '网络接收速率 (RX)', value: formatBandwidth(data.netRx) },
|
||||||
{ key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(data.netTx) }
|
{ key: 'netTx', label: '网络发送速率 (TX)', value: formatBandwidth(data.netTx) }
|
||||||
];
|
];
|
||||||
|
|
||||||
dom.detailMetricsList.innerHTML = metrics.map(m => `
|
dom.detailMetricsList.innerHTML = metrics.map(m => `
|
||||||
@@ -512,7 +523,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-chart-wrapper">
|
<div class="detail-chart-wrapper" style="${m.key === 'cpuBusy' ? 'height: 260px;' : ''}">
|
||||||
<canvas id="chart-${m.key}"></canvas>
|
<canvas id="chart-${m.key}"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ class MetricChart {
|
|||||||
constructor(canvas, unit = '') {
|
constructor(canvas, unit = '') {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
this.data = { timestamps: [], values: [] };
|
this.data = { timestamps: [], values: [], series: null };
|
||||||
this.unit = unit; // '%', 'B/s', etc.
|
this.unit = unit; // '%', 'B/s', etc.
|
||||||
this.dpr = window.devicePixelRatio || 1;
|
this.dpr = window.devicePixelRatio || 1;
|
||||||
this.padding = { top: 10, right: 10, bottom: 20, left: 60 };
|
this.padding = { top: 10, right: 10, bottom: 20, left: 60 };
|
||||||
@@ -305,7 +305,7 @@ class MetricChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setData(data) {
|
setData(data) {
|
||||||
this.data = data || { timestamps: [], values: [] };
|
this.data = data || { timestamps: [], values: [], series: null };
|
||||||
this.animate();
|
this.animate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class MetricChart {
|
|||||||
|
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
const { timestamps, values } = this.data;
|
const { timestamps, values, series } = this.data;
|
||||||
if (!timestamps || timestamps.length < 2) {
|
if (!timestamps || timestamps.length < 2) {
|
||||||
ctx.fillStyle = '#5a6380';
|
ctx.fillStyle = '#5a6380';
|
||||||
ctx.font = '11px sans-serif';
|
ctx.font = '11px sans-serif';
|
||||||
@@ -342,15 +342,21 @@ class MetricChart {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find max with cushion
|
// Determine Y max
|
||||||
let maxVal = Math.max(...values, 0.1);
|
let maxVal = 0;
|
||||||
if (this.unit === '%' && maxVal <= 100) {
|
if (series) {
|
||||||
if (maxVal > 80) maxVal = 100;
|
// For stacked CPU, max sum should be 100
|
||||||
else if (maxVal > 40) maxVal = 80;
|
maxVal = 100;
|
||||||
else if (maxVal > 20) maxVal = 50;
|
|
||||||
else maxVal = 25;
|
|
||||||
} else {
|
} else {
|
||||||
maxVal = maxVal * 1.25;
|
maxVal = Math.max(...values, 0.1);
|
||||||
|
if (this.unit === '%' && maxVal <= 100) {
|
||||||
|
if (maxVal > 80) maxVal = 100;
|
||||||
|
else if (maxVal > 40) maxVal = 80;
|
||||||
|
else if (maxVal > 20) maxVal = 50;
|
||||||
|
else maxVal = 25;
|
||||||
|
} else {
|
||||||
|
maxVal = maxVal * 1.25;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const len = timestamps.length;
|
const len = timestamps.length;
|
||||||
@@ -382,41 +388,102 @@ class MetricChart {
|
|||||||
ctx.fillText(label, p.left - 8, y + 3);
|
ctx.fillText(label, p.left - 8, y + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path
|
if (series) {
|
||||||
ctx.beginPath();
|
// Draw Stacked Area
|
||||||
ctx.moveTo(getX(0), getY(values[0]));
|
const modes = [
|
||||||
for (let i = 1; i < len; i++) {
|
{ name: 'idle', color: 'rgba(100, 116, 139, 0.4)', stroke: '#64748b' }, // Slate
|
||||||
const prevX = getX(i - 1);
|
{ name: 'other', color: 'rgba(168, 85, 247, 0.5)', stroke: '#a855f7' }, // Purple
|
||||||
const currX = getX(i);
|
{ name: 'irq', color: 'rgba(234, 179, 8, 0.5)', stroke: '#eab308' }, // Yellow
|
||||||
const prevY = getY(values[i - 1]);
|
{ name: 'iowait', color: 'rgba(239, 68, 68, 0.5)', stroke: '#ef4444' }, // Red
|
||||||
const currY = getY(values[i]);
|
{ name: 'system', color: 'rgba(6, 182, 212, 0.5)', stroke: '#06b6d4' }, // Cyan
|
||||||
const midX = (prevX + currX) / 2;
|
{ name: 'user', color: 'rgba(99, 102, 241, 0.5)', stroke: '#6366f1' } // Indigo
|
||||||
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
|
];
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke
|
|
||||||
ctx.strokeStyle = '#6366f1';
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.lineJoin = 'round';
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Fill
|
let currentBase = new Array(len).fill(0);
|
||||||
ctx.lineTo(getX(len - 1), p.top + chartH);
|
|
||||||
ctx.lineTo(getX(0), p.top + chartH);
|
modes.forEach(mode => {
|
||||||
ctx.closePath();
|
const vals = series[mode.name];
|
||||||
const grad = ctx.createLinearGradient(0, p.top, 0, p.top + chartH);
|
if (!vals) return;
|
||||||
grad.addColorStop(0, 'rgba(99, 102, 241, 0.15)');
|
|
||||||
grad.addColorStop(1, 'rgba(99, 102, 241, 0)');
|
ctx.beginPath();
|
||||||
ctx.fillStyle = grad;
|
ctx.moveTo(getX(0), getY(currentBase[0] + vals[0]));
|
||||||
ctx.fill();
|
for (let i = 1; i < len; i++) {
|
||||||
|
ctx.lineTo(getX(i), getY(currentBase[i] + vals[i]));
|
||||||
// Last point pulse
|
}
|
||||||
const lastX = getX(len - 1);
|
ctx.lineTo(getX(len - 1), getY(currentBase[len - 1]));
|
||||||
const lastY = getY(values[len - 1]);
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
ctx.beginPath();
|
ctx.lineTo(getX(i), getY(currentBase[i]));
|
||||||
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2);
|
}
|
||||||
ctx.fillStyle = '#6366f1';
|
ctx.closePath();
|
||||||
ctx.fill();
|
ctx.fillStyle = mode.color;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Stroke
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(getX(0), getY(currentBase[0] + vals[0]));
|
||||||
|
for (let i = 1; i < len; i++) {
|
||||||
|
ctx.lineTo(getX(i), getY(currentBase[i] + vals[i]));
|
||||||
|
}
|
||||||
|
ctx.strokeStyle = mode.stroke;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Update base
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
currentBase[i] += vals[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Legend at bottom right
|
||||||
|
ctx.font = '9px sans-serif';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
let lx = w - 10;
|
||||||
|
let ly = h - 5;
|
||||||
|
[...modes].reverse().forEach(m => {
|
||||||
|
ctx.fillStyle = m.stroke;
|
||||||
|
ctx.fillRect(lx - 10, ly - 8, 8, 8);
|
||||||
|
ctx.fillStyle = '#5a6380';
|
||||||
|
ctx.fillText(m.name.charAt(0).toUpperCase() + m.name.slice(1), lx - 15, ly - 1);
|
||||||
|
lx -= 60;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Draw Single Line Path
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(getX(0), getY(values[0]));
|
||||||
|
for (let i = 1; i < len; i++) {
|
||||||
|
const prevX = getX(i - 1);
|
||||||
|
const currX = getX(i);
|
||||||
|
const prevY = getY(values[i - 1]);
|
||||||
|
const currY = getY(values[i]);
|
||||||
|
const midX = (prevX + currX) / 2;
|
||||||
|
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke
|
||||||
|
ctx.strokeStyle = '#6366f1';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
ctx.lineTo(getX(len - 1), p.top + chartH);
|
||||||
|
ctx.lineTo(getX(0), p.top + chartH);
|
||||||
|
ctx.closePath();
|
||||||
|
const grad = ctx.createLinearGradient(0, p.top, 0, p.top + chartH);
|
||||||
|
grad.addColorStop(0, 'rgba(99, 102, 241, 0.15)');
|
||||||
|
grad.addColorStop(1, 'rgba(99, 102, 241, 0)');
|
||||||
|
ctx.fillStyle = grad;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Last point pulse
|
||||||
|
const lastX = getX(len - 1);
|
||||||
|
const lastY = getY(values[len - 1]);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(lastX, lastY, 3, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = '#6366f1';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@@ -421,6 +421,14 @@ async function getServerDetails(baseUrl, instance, job) {
|
|||||||
|
|
||||||
// Queries based on the requested dashboard structure
|
// Queries based on the requested dashboard structure
|
||||||
const queries = {
|
const queries = {
|
||||||
|
// Split CPU
|
||||||
|
cpuSystem: `avg(rate(node_cpu_seconds_total{mode="system", instance="${node}"}[1m])) * 100`,
|
||||||
|
cpuUser: `avg(rate(node_cpu_seconds_total{mode="user", instance="${node}"}[1m])) * 100`,
|
||||||
|
cpuIowait: `avg(rate(node_cpu_seconds_total{mode="iowait", instance="${node}"}[1m])) * 100`,
|
||||||
|
cpuIrq: `avg(rate(node_cpu_seconds_total{mode=~"irq|softirq", instance="${node}"}[1m])) * 100`,
|
||||||
|
cpuOther: `avg(rate(node_cpu_seconds_total{mode=~"nice|steal|guest|guest_nice", instance="${node}"}[1m])) * 100`,
|
||||||
|
cpuIdle: `avg(rate(node_cpu_seconds_total{mode="idle", instance="${node}"}[1m])) * 100`,
|
||||||
|
|
||||||
cpuBusy: `100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle", instance="${node}"}[1m])))`,
|
cpuBusy: `100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle", instance="${node}"}[1m])))`,
|
||||||
sysLoad: `node_load1{instance="${node}",job="${job}"} * 100 / count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`,
|
sysLoad: `node_load1{instance="${node}",job="${job}"} * 100 / count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`,
|
||||||
memUsedPct: `(1 - (node_memory_MemAvailable_bytes{instance="${node}", job="${job}"} / node_memory_MemTotal_bytes{instance="${node}", job="${job}"})) * 100`,
|
memUsedPct: `(1 - (node_memory_MemAvailable_bytes{instance="${node}", job="${job}"} / node_memory_MemTotal_bytes{instance="${node}", job="${job}"})) * 100`,
|
||||||
@@ -455,9 +463,42 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
|
|||||||
const url = normalizeUrl(baseUrl);
|
const url = normalizeUrl(baseUrl);
|
||||||
const node = instance;
|
const node = instance;
|
||||||
|
|
||||||
|
// Custom multi-metric handler for CPU Busy
|
||||||
|
if (metric === 'cpuBusy') {
|
||||||
|
const modes = {
|
||||||
|
system: 'system',
|
||||||
|
user: 'user',
|
||||||
|
iowait: 'iowait',
|
||||||
|
irq: 'irq|softirq',
|
||||||
|
other: 'nice|steal|guest|guest_nice',
|
||||||
|
idle: 'idle'
|
||||||
|
};
|
||||||
|
|
||||||
|
const rangeObj = parseRange(range, start, end);
|
||||||
|
const timestamps = [];
|
||||||
|
const series = {};
|
||||||
|
Object.keys(modes).forEach(m => series[m] = []);
|
||||||
|
|
||||||
|
const results = await Promise.all(Object.entries(modes).map(async ([name, mode]) => {
|
||||||
|
const expr = `avg(rate(node_cpu_seconds_total{mode=~"${mode}", instance="${node}"}[1m])) * 100`;
|
||||||
|
const res = await queryRange(url, expr, rangeObj.queryStart, rangeObj.queryEnd, rangeObj.step);
|
||||||
|
return { name, values: res.length > 0 ? res[0].values : [] };
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (results[0].values.length === 0) return { timestamps: [], series: {} };
|
||||||
|
|
||||||
|
// Use first result for timestamps
|
||||||
|
results[0].values.forEach(v => timestamps.push(v[0] * 1000));
|
||||||
|
|
||||||
|
results.forEach(r => {
|
||||||
|
r.values.forEach(v => series[r.name].push(parseFloat(v[1])));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { timestamps, series };
|
||||||
|
}
|
||||||
|
|
||||||
// Map metric keys to Prometheus expressions
|
// Map metric keys to Prometheus expressions
|
||||||
const metricMap = {
|
const metricMap = {
|
||||||
cpuBusy: `100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle", instance="${node}"}[1m])))`,
|
|
||||||
sysLoad: `node_load1{instance="${node}",job="${job}"} * 100 / count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`,
|
sysLoad: `node_load1{instance="${node}",job="${job}"} * 100 / count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`,
|
||||||
memUsedPct: `(1 - (node_memory_MemAvailable_bytes{instance="${node}", job="${job}"} / node_memory_MemTotal_bytes{instance="${node}", job="${job}"})) * 100`,
|
memUsedPct: `(1 - (node_memory_MemAvailable_bytes{instance="${node}", job="${job}"} / node_memory_MemTotal_bytes{instance="${node}", job="${job}"})) * 100`,
|
||||||
swapUsedPct: `((node_memory_SwapTotal_bytes{instance="${node}",job="${job}"} - node_memory_SwapFree_bytes{instance="${node}",job="${job}"}) / (node_memory_SwapTotal_bytes{instance="${node}",job="${job}"})) * 100`,
|
swapUsedPct: `((node_memory_SwapTotal_bytes{instance="${node}",job="${job}"} - node_memory_SwapFree_bytes{instance="${node}",job="${job}"}) / (node_memory_SwapTotal_bytes{instance="${node}",job="${job}"})) * 100`,
|
||||||
@@ -469,52 +510,10 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
|
|||||||
const expr = metricMap[metric];
|
const expr = metricMap[metric];
|
||||||
if (!expr) throw new Error('Invalid metric for history');
|
if (!expr) throw new Error('Invalid metric for history');
|
||||||
|
|
||||||
let duration, step, queryStart, queryEnd;
|
const rangeObj = parseRange(range, start, end);
|
||||||
|
|
||||||
if (start && end) {
|
|
||||||
queryStart = Math.floor(new Date(start).getTime() / 1000);
|
|
||||||
queryEnd = Math.floor(new Date(end).getTime() / 1000);
|
|
||||||
duration = queryEnd - queryStart;
|
|
||||||
if (duration <= 0) throw new Error('End time must be after start time');
|
|
||||||
// Reasonable step for fixed range
|
|
||||||
step = Math.max(15, Math.floor(duration / 100));
|
|
||||||
} else {
|
|
||||||
// Relative range logic
|
|
||||||
const rangeMap = {
|
|
||||||
'15m': { duration: 900, step: 15 },
|
|
||||||
'30m': { duration: 1800, step: 30 },
|
|
||||||
'1h': { duration: 3600, step: 60 },
|
|
||||||
'6h': { duration: 21600, step: 300 },
|
|
||||||
'12h': { duration: 43200, step: 600 },
|
|
||||||
'24h': { duration: 86400, step: 900 },
|
|
||||||
'2d': { duration: 172800, step: 1800 },
|
|
||||||
'7d': { duration: 604800, step: 3600 }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rangeMap[range]) {
|
|
||||||
duration = rangeMap[range].duration;
|
|
||||||
step = rangeMap[range].step;
|
|
||||||
} else {
|
|
||||||
// Try to parse relative time string like "2h", "30m", "1d"
|
|
||||||
const match = range.match(/^(\d+)([smhd])$/);
|
|
||||||
if (match) {
|
|
||||||
const val = parseInt(match[1]);
|
|
||||||
const unit = match[2];
|
|
||||||
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
||||||
duration = val * (multipliers[unit] || 3600);
|
|
||||||
// Calculate a reasonable step for ~60-120 data points
|
|
||||||
step = Math.max(15, Math.floor(duration / 100));
|
|
||||||
} else {
|
|
||||||
duration = 3600;
|
|
||||||
step = 60;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queryEnd = Math.floor(Date.now() / 1000);
|
|
||||||
queryStart = queryEnd - duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await queryRange(url, expr, queryStart, queryEnd, step);
|
const result = await queryRange(url, expr, rangeObj.queryStart, rangeObj.queryEnd, rangeObj.step);
|
||||||
if (!result || result.length === 0) return { timestamps: [], values: [] };
|
if (!result || result.length === 0) return { timestamps: [], values: [] };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -527,6 +526,48 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseRange(range, start, end) {
|
||||||
|
let duration, step, queryStart, queryEnd;
|
||||||
|
|
||||||
|
if (start && end) {
|
||||||
|
queryStart = Math.floor(new Date(start).getTime() / 1000);
|
||||||
|
queryEnd = Math.floor(new Date(end).getTime() / 1000);
|
||||||
|
duration = queryEnd - queryStart;
|
||||||
|
step = Math.max(15, Math.floor(duration / 100));
|
||||||
|
} else {
|
||||||
|
const rangeMap = {
|
||||||
|
'15m': { duration: 900, step: 15 },
|
||||||
|
'30m': { duration: 1800, step: 30 },
|
||||||
|
'1h': { duration: 3600, step: 60 },
|
||||||
|
'6h': { duration: 21600, step: 300 },
|
||||||
|
'12h': { duration: 43200, step: 600 },
|
||||||
|
'24h': { duration: 86400, step: 900 },
|
||||||
|
'2d': { duration: 172800, step: 1800 },
|
||||||
|
'7d': { duration: 604800, step: 3600 }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rangeMap[range]) {
|
||||||
|
duration = rangeMap[range].duration;
|
||||||
|
step = rangeMap[range].step;
|
||||||
|
} else {
|
||||||
|
const match = range.match(/^(\d+)([smhd])$/);
|
||||||
|
if (match) {
|
||||||
|
const val = parseInt(match[1]);
|
||||||
|
const unit = match[2];
|
||||||
|
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
||||||
|
duration = val * (multipliers[unit] || 3600);
|
||||||
|
step = Math.max(15, Math.floor(duration / 100));
|
||||||
|
} else {
|
||||||
|
duration = 3600;
|
||||||
|
step = 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryEnd = Math.floor(Date.now() / 1000);
|
||||||
|
queryStart = queryEnd - duration;
|
||||||
|
}
|
||||||
|
return { queryStart, queryEnd, step };
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
testConnection,
|
testConnection,
|
||||||
query,
|
query,
|
||||||
|
|||||||
Reference in New Issue
Block a user