mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-04 21:31:23 +08:00
379 lines
18 KiB
HTML
379 lines
18 KiB
HTML
<style>
|
|
.clicd-info{font-size:14px;color:#1f2937;background:#f6f8fb;padding:14px;border-radius:6px;max-width:100%;overflow:hidden}
|
|
.clicd-info *{box-sizing:border-box}
|
|
.clicd-head{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:12px}
|
|
.clicd-mini{background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:10px}
|
|
.clicd-mini-label{font-size:12px;color:#6b7280;margin-bottom:4px}
|
|
.clicd-mini-value{font-size:16px;font-weight:600;color:#111827;word-break:break-all}
|
|
.clicd-section{background:#fff;border:1px solid #e5e7eb;border-radius:6px;margin-top:12px;padding:14px}
|
|
.clicd-section-title{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:15px;font-weight:700;margin-bottom:12px;color:#111827;min-width:0;flex-wrap:wrap}
|
|
.clicd-section-title>span:first-child{min-width:0}
|
|
.clicd-refresh{display:flex;align-items:center;justify-content:flex-end;gap:8px;font-size:12px;color:#6b7280;font-weight:400;flex-wrap:wrap;min-width:0;max-width:100%}
|
|
.clicd-refresh label{display:inline-flex;align-items:center;gap:4px;min-width:0;white-space:nowrap}
|
|
.clicd-refresh-select{height:28px;border:1px solid #d1d5db;border-radius:4px;background:#fff;color:#374151;padding:3px 6px;font-size:12px}
|
|
.clicd-refresh-btn{height:28px;border:1px solid #2563eb;background:#2563eb;color:#fff;border-radius:4px;padding:3px 8px;font-size:12px;cursor:pointer;white-space:nowrap;max-width:96px;overflow:hidden;text-overflow:ellipsis}
|
|
.clicd-refresh-btn[disabled]{opacity:.6;cursor:not-allowed}
|
|
.clicd-gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}
|
|
.clicd-gauge{display:flex;align-items:center;gap:12px;min-height:92px}
|
|
.clicd-ring{--p:0%;width:78px;height:78px;border-radius:50%;background:conic-gradient(#2f80ed var(--p),#e5e7eb 0);display:grid;place-items:center;flex:0 0 auto;position:relative}
|
|
.clicd-ring:before{content:"";width:66px;height:66px;border-radius:50%;background:#fff;position:absolute}
|
|
.clicd-ring span{position:relative;display:inline-flex;align-items:center;justify-content:center;max-width:62px;min-width:0;font-size:17px;font-weight:700;line-height:1;color:#111827;white-space:nowrap;text-align:center;background:#fff;border-radius:3px;padding:0 1px}
|
|
.clicd-ring[data-tight="1"] span{font-size:15px}
|
|
.clicd-ring[data-tight="2"] span{font-size:14px}
|
|
.clicd-gauge-title{font-weight:700;color:#111827;margin-bottom:4px}
|
|
.clicd-gauge-sub{font-size:12px;color:#6b7280;line-height:1.45}
|
|
.clicd-progress{height:12px;background:#e5e7eb;border-radius:999px;overflow:hidden}
|
|
.clicd-progress span{display:block;height:100%;width:0;background:linear-gradient(90deg,#2f80ed,#10b981);transition:width .25s ease}
|
|
.clicd-traffic-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:center;margin-top:8px;color:#374151;min-width:0}
|
|
.clicd-traffic-row>div{min-width:0;word-break:break-word}
|
|
.clicd-charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}
|
|
.clicd-chart{border:1px solid #e5e7eb;border-radius:6px;padding:12px;background:#fff;min-height:190px;min-width:0;overflow:hidden}
|
|
.clicd-chart-title{display:flex;justify-content:space-between;gap:8px;align-items:center;font-weight:700;margin-bottom:8px;color:#111827;min-width:0;flex-wrap:wrap}
|
|
.clicd-chart-value{font-size:12px;color:#6b7280;font-weight:400;white-space:normal;overflow-wrap:anywhere;text-align:right}
|
|
.clicd-chart canvas{width:100%;height:132px;display:block}
|
|
.clicd-table{width:100%;border-collapse:collapse;background:#fff}
|
|
.clicd-table th,.clicd-table td{border:1px solid #e5e7eb;padding:8px;text-align:left}
|
|
.clicd-table th{width:16%;background:#f9fafb;color:#374151;font-weight:600}
|
|
.clicd-debug{display:none;margin-top:10px;padding:8px;background:#fff7ed;border:1px solid #fed7aa;color:#9a3412;border-radius:6px;font-size:12px}
|
|
@media (max-width:640px){
|
|
.clicd-info{padding:10px}
|
|
.clicd-section-title{align-items:flex-start}
|
|
.clicd-refresh{justify-content:flex-start;width:100%}
|
|
.clicd-charts{grid-template-columns:1fr}
|
|
.clicd-table th,.clicd-table td{display:block;width:100%}
|
|
.clicd-ring{width:70px;height:70px}
|
|
.clicd-ring:before{width:60px;height:60px}
|
|
.clicd-ring span{max-width:56px;font-size:15px}
|
|
}
|
|
</style>
|
|
|
|
<div class="clicd-info" data-clicd-info-root="1">
|
|
<div class="clicd-head">
|
|
<div class="clicd-mini">
|
|
<div class="clicd-mini-label">实例名称</div>
|
|
<div class="clicd-mini-value">{$container.name|default='-'}</div>
|
|
</div>
|
|
<div class="clicd-mini">
|
|
<div class="clicd-mini-label">运行状态</div>
|
|
<div class="clicd-mini-value">{$status_text|default='-'}</div>
|
|
</div>
|
|
<div class="clicd-mini">
|
|
<div class="clicd-mini-label">SSH 地址</div>
|
|
<div class="clicd-mini-value">{$ssh_host|default='-'}:{$ssh_port|default='-'}</div>
|
|
</div>
|
|
<div class="clicd-mini">
|
|
<div class="clicd-mini-label">IPv6</div>
|
|
<div class="clicd-mini-value">{$ipv6|default='-'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="clicd-section">
|
|
<div class="clicd-section-title">
|
|
<span>状态</span>
|
|
<span class="clicd-refresh">
|
|
<span>更新于 <span data-clicd-info="chart_time">-</span></span>
|
|
<label>
|
|
自动刷新
|
|
<select class="clicd-refresh-select" id="clicd-info-refresh">
|
|
<option value="0" selected>不刷新</option>
|
|
<option value="10000">10 秒</option>
|
|
<option value="60000">1 分钟</option>
|
|
<option value="300000">5 分钟</option>
|
|
<option value="600000">10 分钟</option>
|
|
</select>
|
|
</label>
|
|
<button class="clicd-refresh-btn" type="button" id="clicd-info-refresh-now">立即刷新</button>
|
|
</span>
|
|
</div>
|
|
<div class="clicd-gauges">
|
|
<div class="clicd-gauge">
|
|
<div class="clicd-ring" data-gauge="cpu_percent"><span><span data-clicd-info="cpu_percent">0</span>%</span></div>
|
|
<div>
|
|
<div class="clicd-gauge-title">CPU</div>
|
|
<div class="clicd-gauge-sub" data-clicd-info="cpu_detail">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="clicd-gauge">
|
|
<div class="clicd-ring" data-gauge="mem_percent"><span><span data-clicd-info="mem_percent">0</span>%</span></div>
|
|
<div>
|
|
<div class="clicd-gauge-title">内存</div>
|
|
<div class="clicd-gauge-sub" data-clicd-info="mem_detail">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="clicd-gauge">
|
|
<div class="clicd-ring" data-gauge="load_percent"><span><span data-clicd-info="load_percent">0</span>%</span></div>
|
|
<div>
|
|
<div class="clicd-gauge-title">负载</div>
|
|
<div class="clicd-gauge-sub" data-clicd-info="load_detail">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="clicd-gauge">
|
|
<div class="clicd-ring" data-gauge="disk_percent"><span><span data-clicd-info="disk_percent">0</span>%</span></div>
|
|
<div>
|
|
<div class="clicd-gauge-title">磁盘</div>
|
|
<div class="clicd-gauge-sub" data-clicd-info="disk_detail">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top:14px">
|
|
<div class="clicd-traffic-row">
|
|
<div>月流量</div>
|
|
<div><span data-clicd-info="traffic_used_text">{$traffic_used_text|default='-'}</span> / <span data-clicd-info="traffic_limit_text">{$traffic_limit_text|default='-'}</span></div>
|
|
</div>
|
|
<div class="clicd-progress"><span data-progress="traffic_percent"></span></div>
|
|
<div class="clicd-traffic-row" style="font-size:12px;color:#6b7280">
|
|
<div>入站 <span data-clicd-info="traffic_in_text">{$traffic_in_text|default='-'}</span></div>
|
|
<div>出站 <span data-clicd-info="traffic_out_text">{$traffic_out_text|default='-'}</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="clicd-section">
|
|
<div class="clicd-section-title"><span>统计信息</span></div>
|
|
<div class="clicd-charts">
|
|
<div class="clicd-chart">
|
|
<div class="clicd-chart-title">CPU 使用率 <span class="clicd-chart-value" data-clicd-info="cpu_detail">-</span></div>
|
|
<canvas data-chart="cpu_percent"></canvas>
|
|
</div>
|
|
<div class="clicd-chart">
|
|
<div class="clicd-chart-title">内存使用 <span class="clicd-chart-value" data-clicd-info="mem_detail">-</span></div>
|
|
<canvas data-chart="mem_percent"></canvas>
|
|
</div>
|
|
<div class="clicd-chart">
|
|
<div class="clicd-chart-title">网络流量 <span class="clicd-chart-value"><span data-clicd-info="net_in_rate">0 B/s</span> / <span data-clicd-info="net_out_rate">0 B/s</span></span></div>
|
|
<canvas data-chart="network"></canvas>
|
|
</div>
|
|
<div class="clicd-chart">
|
|
<div class="clicd-chart-title">磁盘 IO <span class="clicd-chart-value"><span data-clicd-info="disk_read_rate">0 B/s</span> / <span data-clicd-info="disk_write_rate">0 B/s</span></span></div>
|
|
<canvas data-chart="diskio"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="clicd-section">
|
|
<div class="clicd-section-title"><span>实例信息</span></div>
|
|
<table class="clicd-table">
|
|
<tbody>
|
|
<tr>
|
|
<th>IPv4</th><td>{$ipv4|default='-'}</td>
|
|
<th>用户名</th><td>root</td>
|
|
</tr>
|
|
<tr>
|
|
<th>SSH 端口</th><td>{$ssh_port|default='-'}</td>
|
|
<th>SSH 密码</th><td>{$ssh_password|default='-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>CPU</th><td>{$vcpu|default='-'} 核</td>
|
|
<th>内存</th><td>{$ram_mb|default='-'} MB</td>
|
|
</tr>
|
|
<tr>
|
|
<th>硬盘</th><td>{$disk_gb|default='-'} GB</td>
|
|
<th>带宽</th><td>{$bandwidth|default='-'} Mbps</td>
|
|
</tr>
|
|
<tr>
|
|
<th>到期时间</th><td colspan="3">{$expires_at|default='-'}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="clicd-debug" id="clicd-info-debug"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
var root = document.querySelector('[data-clicd-info-root="1"]:not([data-info-bound="1"])');
|
|
if (!root) return;
|
|
root.setAttribute('data-info-bound', '1');
|
|
|
|
var history = {
|
|
cpu_percent: [],
|
|
mem_percent: [],
|
|
network_in: [],
|
|
network_out: [],
|
|
disk_read: [],
|
|
disk_write: []
|
|
};
|
|
var maxPoints = 18;
|
|
var refreshTimer = null;
|
|
|
|
function endpoint() {
|
|
return "{$MODULE_CUSTOM_API}";
|
|
}
|
|
|
|
function number(value) {
|
|
var n = parseFloat(value);
|
|
return isFinite(n) ? n : 0;
|
|
}
|
|
|
|
function push(name, value) {
|
|
history[name].push(number(value));
|
|
if (history[name].length > maxPoints) history[name].shift();
|
|
}
|
|
|
|
function setText(key, value) {
|
|
root.querySelectorAll('[data-clicd-info="' + key + '"]').forEach(function(node){
|
|
if (typeof value !== 'object') node.textContent = value;
|
|
});
|
|
}
|
|
|
|
function setGauge(key, value) {
|
|
var pct = Math.max(0, Math.min(100, number(value)));
|
|
root.querySelectorAll('[data-gauge="' + key + '"]').forEach(function(node){
|
|
node.style.setProperty('--p', pct + '%');
|
|
});
|
|
}
|
|
|
|
function fitGaugeText() {
|
|
root.querySelectorAll('.clicd-ring').forEach(function(ring){
|
|
var label = ring.querySelector('span');
|
|
if (!label) return;
|
|
ring.removeAttribute('data-tight');
|
|
if (label.scrollWidth > label.clientWidth) {
|
|
ring.setAttribute('data-tight', '1');
|
|
}
|
|
if (label.scrollWidth > label.clientWidth) {
|
|
ring.setAttribute('data-tight', '2');
|
|
}
|
|
});
|
|
}
|
|
|
|
function setProgress(key, value) {
|
|
var pct = Math.max(0, Math.min(100, number(value)));
|
|
root.querySelectorAll('[data-progress="' + key + '"]').forEach(function(node){
|
|
node.style.width = pct + '%';
|
|
});
|
|
}
|
|
|
|
function draw(canvas, series, colors, maxValue) {
|
|
if (!canvas || !canvas.getContext) return;
|
|
var rect = canvas.getBoundingClientRect();
|
|
var ratio = window.devicePixelRatio || 1;
|
|
var width = Math.max(220, Math.floor(rect.width || canvas.clientWidth || 220));
|
|
var height = Math.max(120, Math.floor(rect.height || canvas.clientHeight || 132));
|
|
if (canvas.width !== width * ratio || canvas.height !== height * ratio) {
|
|
canvas.width = width * ratio;
|
|
canvas.height = height * ratio;
|
|
}
|
|
var ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
ctx.clearRect(0, 0, width, height);
|
|
ctx.strokeStyle = '#e5e7eb';
|
|
ctx.lineWidth = 1;
|
|
for (var i = 1; i < 4; i++) {
|
|
var y = Math.round((height / 4) * i);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y);
|
|
ctx.lineTo(width, y);
|
|
ctx.stroke();
|
|
}
|
|
series.forEach(function(values, idx){
|
|
if (!values.length) return;
|
|
var color = colors[idx] || '#2f80ed';
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
if (values.length === 1) {
|
|
var singleY = height - (Math.max(0, Math.min(maxValue, number(values[0]))) / maxValue) * (height - 6) - 3;
|
|
ctx.moveTo(0, singleY);
|
|
ctx.lineTo(width, singleY);
|
|
ctx.stroke();
|
|
ctx.fillStyle = color;
|
|
ctx.beginPath();
|
|
ctx.arc(width - 8, singleY, 3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
return;
|
|
}
|
|
values.forEach(function(value, i){
|
|
var x = values.length <= 1 ? width : (i / (values.length - 1)) * width;
|
|
var y = height - (Math.max(0, Math.min(maxValue, number(value))) / maxValue) * (height - 6) - 3;
|
|
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
});
|
|
ctx.stroke();
|
|
});
|
|
}
|
|
|
|
function redraw() {
|
|
draw(root.querySelector('[data-chart="cpu_percent"]'), [history.cpu_percent], ['#2f80ed'], 100);
|
|
draw(root.querySelector('[data-chart="mem_percent"]'), [history.mem_percent], ['#10b981'], 100);
|
|
var netMax = Math.max(1, Math.max.apply(null, history.network_in.concat(history.network_out, [1])));
|
|
draw(root.querySelector('[data-chart="network"]'), [history.network_in, history.network_out], ['#2f80ed', '#f59e0b'], netMax);
|
|
var ioMax = Math.max(1, Math.max.apply(null, history.disk_read.concat(history.disk_write, [1])));
|
|
draw(root.querySelector('[data-chart="diskio"]'), [history.disk_read, history.disk_write], ['#10b981', '#ef4444'], ioMax);
|
|
}
|
|
|
|
function showInfoError(text) {
|
|
var debug = document.getElementById('clicd-info-debug');
|
|
if (debug) {
|
|
debug.style.display = 'block';
|
|
debug.textContent = text || 'info load failed';
|
|
}
|
|
}
|
|
|
|
function loadInfo() {
|
|
var refreshNow = document.getElementById('clicd-info-refresh-now');
|
|
if (refreshNow) refreshNow.disabled = true;
|
|
var body = new URLSearchParams();
|
|
body.set('id', '{$service_id}');
|
|
body.set('func', 'infoData');
|
|
fetch(endpoint(), {
|
|
method:'POST',
|
|
headers:{
|
|
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
|
|
'Authorization':'JWT {$Think.get.jwt}'
|
|
},
|
|
credentials:'same-origin',
|
|
body: body.toString()
|
|
})
|
|
.then(function(res){ return res.json(); })
|
|
.then(function(json){
|
|
if (!json || (json.status !== 200 && json.status !== 'success') || !json.data) {
|
|
showInfoError(json && json.msg ? json.msg : 'info load failed');
|
|
return;
|
|
}
|
|
var data = json.data;
|
|
Object.keys(data).forEach(function(key){ setText(key, data[key]); });
|
|
['cpu_percent','mem_percent','load_percent','disk_percent'].forEach(function(key){ setGauge(key, data[key]); });
|
|
fitGaugeText();
|
|
setProgress('traffic_percent', data.traffic_percent);
|
|
push('cpu_percent', data.cpu_percent);
|
|
push('mem_percent', data.mem_percent);
|
|
push('network_in', data.net_in_bps);
|
|
push('network_out', data.net_out_bps);
|
|
push('disk_read', data.disk_read_bps);
|
|
push('disk_write', data.disk_write_bps);
|
|
redraw();
|
|
})
|
|
.catch(function(error){
|
|
showInfoError(error && error.message ? error.message : 'info request failed');
|
|
})
|
|
.finally(function(){
|
|
if (refreshNow) refreshNow.disabled = false;
|
|
});
|
|
}
|
|
|
|
loadInfo();
|
|
var refreshSelect = document.getElementById('clicd-info-refresh');
|
|
var refreshNow = document.getElementById('clicd-info-refresh-now');
|
|
if (refreshNow) {
|
|
refreshNow.addEventListener('click', loadInfo);
|
|
}
|
|
if (refreshSelect) {
|
|
refreshSelect.addEventListener('change', function(){
|
|
if (refreshTimer) {
|
|
window.clearInterval(refreshTimer);
|
|
refreshTimer = null;
|
|
}
|
|
var ms = number(refreshSelect.value);
|
|
if (ms > 0) {
|
|
loadInfo();
|
|
refreshTimer = window.setInterval(loadInfo, ms);
|
|
}
|
|
});
|
|
}
|
|
window.addEventListener('resize', function(){ window.setTimeout(function(){ fitGaugeText(); redraw(); }, 50); });
|
|
})();
|
|
</script>
|