From d31827fc19d5de83911a06df489f1c98bfd387c1 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Tue, 1 Sep 2026 21:16:26 +0800 Subject: [PATCH] =?UTF-8?q?web:=20pure/flat=20redesign=20=E2=80=94=20dotte?= =?UTF-8?q?d=20rotating=20earth=20globe,=20PMM=20intro=20left/globe=20righ?= =?UTF-8?q?t,=20live=20server=20status=20(SZ=20120.24.78.176=20/=20HK=2038?= =?UTF-8?q?.76.190.153)=20via=20status.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/assets/main.js | 174 ++++++++++++++++++++++++++++++------------- web/assets/style.css | 153 ++++++++++++++++++------------------- web/index.php | 91 ++++++++++++---------- web/status.php | 65 ++++++++++++++++ 4 files changed, 310 insertions(+), 173 deletions(-) create mode 100644 web/status.php diff --git a/web/assets/main.js b/web/assets/main.js index 2d8e85b..31cf1ad 100644 --- a/web/assets/main.js +++ b/web/assets/main.js @@ -1,4 +1,4 @@ -/* PMM site — world map island glow + install tabs + copy buttons */ +/* PMM site — rotating dotted Earth globe + live status + tabs + copy */ (function () { "use strict"; @@ -32,8 +32,7 @@ var ta = document.createElement("textarea"); ta.value = t; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); - try { document.execCommand("copy"); flash("已复制"); } - catch (e) { flash("复制失败"); } + try { document.execCommand("copy"); flash("已复制"); } catch (e) { flash("复制失败"); } document.body.removeChild(ta); } function flash(msg) { @@ -43,60 +42,129 @@ }); }); - /* ---------- World map: flood-fill island glow ---------- */ - var mapEl = document.getElementById("worldmap"); - if (!mapEl) return; - var rows = mapEl.querySelectorAll(".row"); - var grid = []; - var R = grid.length, C = 0; - rows.forEach(function (rowEl) { - var arr = Array.prototype.slice.call(rowEl.children); - grid.push(arr); - C = Math.max(C, arr.length); - }); - R = grid.length; - - function isLand(r, c) { - if (r < 0 || r >= R || c < 0 || c >= C) return false; - var el = grid[r][c]; - return el && el.classList.contains("land"); - } - - function clearHot() { - mapEl.querySelectorAll(".px.hot,.px.hot2").forEach(function (el) { - el.classList.remove("hot", "hot2"); - el.style.transitionDelay = ""; + /* ---------- Rotating dotted Earth globe ---------- */ + var cv = document.getElementById("globe"); + if (cv && window.PMM_MAP && window.PMM_DIMS) { + var ctx = cv.getContext("2d"); + var R = window.PMM_DIMS.rows, C = window.PMM_DIMS.cols; + var DG = Math.PI / 180; + // map cell -> lat/lon + var pts = window.PMM_MAP.map(function (p) { + return { + lat: (76 - p[0] * (152 / R)) * DG, + lon: (-180 + p[1] * (360 / C)) * DG + }; }); - } + // server locations (Shenzhen ~22.5N/114E, HK ~22.3N/114.2E) + var servers = [ + { lat: 22.54 * DG, lon: 114.06 * DG }, + { lat: 22.32 * DG, lon: 114.17 * DG } + ]; - function light(r, c) { - clearHot(); - if (!isLand(r, c)) return; - // BFS over the island (8-connectivity), staggered outward for a "flood" look - var seen = {}, queue = [[r, c, 0]]; - seen[r + "," + c] = 1; - var dirs = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]; - while (queue.length) { - var cell = queue.shift(); - var cr = cell[0], cc = cell[1], d = cell[2]; - var el = grid[cr][cc]; - var hot = d % 3 === 1 ? "hot2" : "hot"; - el.style.transitionDelay = Math.min(d * 12, 320) + "ms"; - el.classList.remove("hot", "hot2"); - el.classList.add(hot); - for (var i = 0; i < dirs.length; i++) { - var nr = cr + dirs[i][0], nc = cc + dirs[i][1]; - var k = nr + "," + nc; - if (isLand(nr, nc) && !seen[k]) { seen[k] = 1; queue.push([nr, nc, d + 1]); } + var W, H, cx, cy, radius; + function resize() { + var dpr = Math.min(window.devicePixelRatio || 1, 2); + var s = cv.clientWidth || 320; + W = cv.width = s * dpr; H = cv.height = s * dpr; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + cx = s / 2; cy = s / 2; + radius = s / 2 - 6; + } + resize(); + window.addEventListener("resize", resize); + + var theta = 0, rafId = null; + var ACC = "#3ea8ff", OKC = "#2dd4a7"; + + function draw(t) { + theta += 0.0035; + var cos = Math.cos, sin = Math.sin; + ctx.clearRect(0, 0, W, H); + + // sphere disc + ctx.beginPath(); + ctx.arc(cx, cy, radius, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(62,168,255,0.045)"; + ctx.fill(); + ctx.strokeStyle = "rgba(62,168,255,0.20)"; + ctx.lineWidth = 1; + ctx.stroke(); + + // faint equator + meridian rings for a globe feel + ctx.strokeStyle = "rgba(62,168,255,0.10)"; + ctx.beginPath(); ctx.ellipse(cx, cy, radius, radius * 0.98, 0, 0, 2 * Math.PI); ctx.stroke(); + + function proj(lat, lon) { + var r = lon + theta; + var x = cos(lat) * sin(r); + var y = sin(lat); + var z = cos(lat) * cos(r); + return { x: x, y: y, z: z }; } + + // land dot cloud — back pass (z<0) dim, front pass (z>0) bright + for (var pass = 0; pass < 2; pass++) { + var front = pass === 1; + for (var i = 0; i < pts.length; i++) { + var q = proj(pts[i].lat, pts[i].lon); + if ((q.z > 0) !== front) continue; + ctx.fillStyle = front ? "rgba(62,168,255,0.85)" : "rgba(62,168,255,0.18)"; + ctx.fillRect(cx + q.x * radius, cy - q.y * radius, 1.6, 1.6); + } + } + + // server location markers (pulse) — only if on the visible hemisphere + servers.forEach(function (s) { + var q = proj(s.lat, s.lon); + if (q.z <= 0) return; + var sx = cx + q.x * radius, sy = cy - q.y * radius; + var pulse = 0.6 + 0.4 * sin(t * 0.006); + ctx.beginPath(); + ctx.arc(sx, sy, 3, 0, 2 * Math.PI); + ctx.fillStyle = OKC; + ctx.fill(); + ctx.beginPath(); + ctx.arc(sx, sy, 4 + (1 - pulse) * 4, 0, 2 * Math.PI); + ctx.strokeStyle = "rgba(45,212,167," + (0.5 * pulse + 0.15) + ")"; + ctx.lineWidth = 1.5; + ctx.stroke(); + }); + + rafId = requestAnimationFrame(draw); + } + + // only animate while visible + if ("IntersectionObserver" in window) { + var io = new IntersectionObserver(function (en) { + en.forEach(function (e) { + if (e.isIntersecting) { if (!rafId) rafId = requestAnimationFrame(draw); } + else if (rafId) { cancelAnimationFrame(rafId); rafId = null; } + }); + }, { threshold: 0.1 }); + io.observe(cv); + } else { + rafId = requestAnimationFrame(draw); } } - mapEl.addEventListener("mouseover", function (e) { - var el = e.target; - if (el && el.classList && el.classList.contains("land")) { - light(parseInt(el.getAttribute("data-r"), 10), parseInt(el.getAttribute("data-c"), 10)); - } - }); - mapEl.addEventListener("mouseleave", clearHot); + /* ---------- Live mirror status ---------- */ + function loadStatus() { + fetch("status.php", { cache: "no-store" }) + .then(function (r) { return r.json(); }) + .then(function (data) { + if (!Array.isArray(data)) return; + data.forEach(function (s, i) { + var dot = document.getElementById("dot-" + i); + var meta = document.getElementById("meta-" + i); + if (!dot || !meta) return; + dot.className = "status-dot " + (s.online ? "up" : "down"); + meta.innerHTML = s.online + ? "在线 · " + s.ms + " ms · HTTP " + s.code + : "离线 / 不可达"; + }); + }) + .catch(function () {}); + } + loadStatus(); + setInterval(loadStatus, 12000); })(); diff --git a/web/assets/style.css b/web/assets/style.css index 141294b..9f1040e 100644 --- a/web/assets/style.css +++ b/web/assets/style.css @@ -1,17 +1,19 @@ -/* ParlzPackageManager — dark, modern-minimal */ +/* ParlzPackageManager — dark, pure flat solid colors (no gradients) */ :root{ - --bg:#0b0e14; /* near-black */ - --bg2:#10141f; - --panel:#121826; - --panel2:#16202f; - --line:#1e2839; - --text:#e6edf3; - --muted:#8a94a6; - --dim:#5b6574; - --accent:#56d4ff; /* cyan */ - --accent2:#8b5cf6; /* violet */ - --ok:#34d399; + --bg:#0b0e14; + --bg2:#0e131c; + --panel:#121821; + --panel2:#161d29; + --line:#1d2734; + --text:#e8eef6; + --muted:#93a1b3; + --dim:#5a6675; + --accent:#3ea8ff; /* solid blue */ + --accent-dim:#2b6fb0; + --ok:#2dd4a7; + --warn:#f5b13d; + --bad:#f65e5e; --radius:14px; --maxw:1120px; } @@ -19,106 +21,93 @@ *{box-sizing:border-box;margin:0;padding:0} html{scroll-behavior:smooth} body{ - background:var(--bg); - color:var(--text); + background:var(--bg);color:var(--text); font-family:Inter,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC","Microsoft YaHei",sans-serif; - -webkit-font-smoothing:antialiased; - line-height:1.6; - overflow-x:hidden; + -webkit-font-smoothing:antialiased;line-height:1.6;overflow-x:hidden; } a{color:inherit;text-decoration:none} code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Courier New",monospace;font-size:.92em;color:var(--accent)} ::selection{background:var(--accent);color:#04121a} -/* glow backdrop */ -body::before{ - content:"";position:fixed;inset:0;z-index:-1;pointer-events:none; - background: - radial-gradient(700px 420px at 78% -6%, rgba(139,92,246,.16), transparent 60%), - radial-gradient(720px 460px at 12% 4%, rgba(86,212,255,.13), transparent 60%); -} - /* nav */ -.nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);background:rgba(11,14,20,.72); - border-bottom:1px solid var(--line)} -.nav{display:flex;align-items:center;justify-content:space-between;max-width:var(--maxw);margin:0 auto; - padding:14px 22px} +.nav{position:sticky;top:0;z-index:50;background:rgba(11,14,20,.9);border-bottom:1px solid var(--line); + display:flex;align-items:center;justify-content:space-between;max-width:var(--maxw);margin:0 auto;padding:14px 22px} .brand{display:flex;align-items:center;gap:10px} -.brand-mark{font-family:ui-monospace,monospace;font-weight:800;font-size:20px;color:#04121a; - background:linear-gradient(135deg,var(--accent),var(--accent2));padding:2px 9px;border-radius:8px} -.brand-name{font-weight:700;letter-spacing:.2px} +.brand-mark{font-family:ui-monospace,monospace;font-weight:800;font-size:20px;color:#04121a;background:var(--accent); + padding:2px 9px;border-radius:8px} +.brand-name{font-weight:700} .nav-links{display:flex;align-items:center;gap:22px;list-style:none} .nav-links a{color:var(--muted);font-size:14.5px;transition:color .2s} .nav-links a:hover{color:var(--text)} -/* buttons */ -.btn{display:inline-block;padding:11px 20px;border-radius:11px;font-weight:600;font-size:14.5px; - transition:transform .15s,box-shadow .2s,background .2s;border:1px solid transparent} -.btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#04121a} -.btn-primary:hover{transform:translateY(-2px);box-shadow:0 10px 30px -10px rgba(86,212,255,.5)} +/* buttons — solid, flat */ +.btn{display:inline-block;padding:11px 20px;border-radius:11px;font-weight:600;font-size:14.5px;transition:transform .15s,box-shadow .2s;border:1px solid transparent;cursor:pointer} +.btn-primary{background:var(--accent);color:#04121a} +.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 22px -8px rgba(62,168,255,.6)} .btn-ghost{background:transparent;border-color:var(--line);color:var(--text)} .btn-ghost:hover{border-color:var(--accent);color:var(--accent)} .btn-sm{padding:8px 14px;font-size:13px} -/* hero */ -.hero{max-width:var(--maxw);margin:0 auto;padding:74px 22px 30px;text-align:center} -.hero-badge{display:inline-block;font-size:12.5px;letter-spacing:.5px;color:var(--muted); - border:1px solid var(--line);border-radius:999px;padding:5px 14px;margin-bottom:20px} -.hero h1{font-size:clamp(34px,6vw,58px);font-weight:800;letter-spacing:-1px;line-height:1.08} -.accent{background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text; - background-clip:text;color:transparent} -.hero-tag{max-width:720px;margin:20px auto 0;color:var(--muted);font-size:17.5px} -.hero-cta{display:flex;gap:14px;justify-content:center;margin-top:30px;flex-wrap:wrap} +/* hero — two columns */ +.hero{max-width:var(--maxw);margin:0 auto;padding:70px 22px 40px} +.hero-inner{display:grid;grid-template-columns:1.05fr 0.95fr;gap:40px;align-items:center} +.hero-badge{display:inline-block;font-size:12.5px;letter-spacing:.4px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:5px 14px;margin-bottom:20px} +.hero h1{font-size:clamp(32px,5vw,52px);font-weight:800;letter-spacing:-1px;line-height:1.08} +.accent{color:var(--accent)} +.hero-tag{max-width:560px;margin:18px 0 0;color:var(--muted);font-size:17px} +.hero-cta{display:flex;gap:14px;margin-top:28px;flex-wrap:wrap} +.hero-note{margin-top:24px;font-size:14px;color:var(--muted);display:flex;align-items:center;gap:8px} +.dot{width:9px;height:9px;border-radius:50%;display:inline-block;background:var(--dim)} +.dot.ok{background:var(--ok);box-shadow:0 0 0 3px rgba(45,212,167,.18)} -/* world map */ -.map-section{padding:28px 22px 10px} -.map-wrap{max-width:var(--maxw);margin:0 auto;text-align:center} -.map-title{font-size:13px;letter-spacing:4px;text-transform:uppercase;color:var(--dim);margin-bottom:22px} -.worldmap{display:inline-block;font-family:"JetBrains Mono",ui-monospace,monospace; - font-size:clamp(6px,1.05vw,11px);line-height:1;color:var(--dim);user-select:none} -.worldmap .row{display:flex} -.px{display:inline-flex;align-items:center;justify-content:center;width:1ch;height:1.15em} -.px.water{color:transparent} -.px.land{color:var(--dim);transition:color .1s,text-shadow .12s} -.worldmap:hover .px.land{color:#2b3749} /* dim until you engage */ -.px.land.hot{color:var(--accent);text-shadow:0 0 9px rgba(86,212,255,.85),0 0 2px #fff} -.px.land.hot2{color:var(--accent2);text-shadow:0 0 9px rgba(139,92,246,.85)} -.map-hint{margin-top:16px;font-size:13px;color:var(--dim)} +/* globe */ +.hero-globe{position:relative;display:flex;align-items:center;justify-content:center; + aspect-ratio:1;max-width:430px;margin:0 auto;width:100%} +#globe{width:100%;height:100%;display:block} +.globe-caption{position:absolute;bottom:-6px;left:0;right:0;text-align:center;font-size:13px;color:var(--dim);letter-spacing:.5px} -/* sections */ +/* status */ .section{padding:64px 22px} -.section.alt{background:linear-gradient(180deg,transparent,rgba(22,32,47,.55),transparent)} +.section.alt{background:var(--bg2);border-top:1px solid var(--line);border-bottom:1px solid var(--line)} .wrap{max-width:var(--maxw);margin:0 auto} .section-title{text-align:center;font-size:clamp(26px,3.5vw,36px);font-weight:800;letter-spacing:-.5px} .section-sub{text-align:center;color:var(--muted);max-width:640px;margin:12px auto 34px} +.status-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;max-width:760px;margin:0 auto} +.status-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px} +.status-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px} +.pin{font-weight:800;color:var(--text);font-size:18px} +.status-dot{width:14px;height:14px;border-radius:50%;background:var(--dim)} +.status-dot.up{background:var(--ok);box-shadow:0 0 0 4px rgba(45,212,167,.16);animation:pulse 1.6s infinite} +.status-dot.down{background:var(--bad);box-shadow:0 0 0 4px rgba(246,94,94,.16)} +@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(45,212,167,.4)}70%{box-shadow:0 0 0 8px rgba(45,212,167,0)}100%{box-shadow:0 0 0 0 rgba(45,212,167,0)}} +.status-ip{font-family:ui-monospace,monospace;font-size:20px;font-weight:700;color:var(--text)} +.status-host{font-family:ui-monospace,monospace;font-size:13px;color:var(--muted);margin-top:3px} +.status-meta{margin-top:14px;font-size:14px;color:var(--muted)} +.status-meta b{color:var(--text)} + +/* grid cards */ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px} -.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px; - transition:transform .2s,border-color .2s} +.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px;transition:transform .2s,border-color .2s} .card:hover{transform:translateY(-3px);border-color:var(--accent)} .card h3{font-size:17px;margin-bottom:8px} .card p{color:var(--muted);font-size:14.5px} -/* install tabs */ -.tabs{display:inline-flex;gap:6px;background:var(--panel);border:1px solid var(--line); - border-radius:12px;padding:5px;margin:0 auto 18px;display:flex} -.tab{background:transparent;border:0;color:var(--muted);padding:8px 18px;border-radius:8px;cursor:pointer; - font-size:14px;font-weight:600;transition:background .2s,color .2s} -.tab.active{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#04121a} -.code-box{position:relative;max-width:820px;margin:0 auto;background:#0a0d12;border:1px solid var(--line); - border-radius:var(--radius);padding:30px 24px 24px;text-align:left} +/* tabs */ +.tabs{display:flex;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:5px;margin:0 auto 18px;width:max-content} +.tab{background:transparent;border:0;color:var(--muted);padding:8px 18px;border-radius:8px;cursor:pointer;font-size:14px;font-weight:600;transition:background .2s,color .2s} +.tab.active{background:var(--accent);color:#04121a} +.code-box{position:relative;max-width:820px;margin:0 auto;background:#0a0e15;border:1px solid var(--line);border-radius:var(--radius);padding:30px 24px 24px;text-align:left} .code-box[data-pane]{display:none} .code-box[data-pane][data-active]{display:block} .code-box pre{overflow-x:auto} .code-box code{color:#d7e3f4;font-size:14px;white-space:pre} -.copy-btn{position:absolute;top:12px;right:12px;background:var(--panel2);border:1px solid var(--line); - color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer;transition:all .15s} +.copy-btn{position:absolute;top:12px;right:12px;background:var(--panel2);border:1px solid var(--line);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12.5px;cursor:pointer;transition:all .15s} .copy-btn:hover{color:var(--accent);border-color:var(--accent)} .copy-btn.ok{color:var(--ok);border-color:var(--ok)} /* download */ .dl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;max-width:920px;margin:0 auto} -.dl-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius); - padding:22px;display:flex;flex-direction:column;gap:6px;transition:transform .2s,border-color .2s} +.dl-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px;display:flex;flex-direction:column;gap:6px;transition:transform .2s,border-color .2s} .dl-card:hover{transform:translateY(-3px);border-color:var(--accent)} .dl-os{font-size:13px;color:var(--accent);font-weight:700} .dl-file{font-family:ui-monospace,monospace;color:var(--text);font-size:14px;word-break:break-all} @@ -126,13 +115,11 @@ body::before{ /* mirror */ .mirror-list{max-width:820px;margin:0 auto 24px;display:grid;gap:12px} -.mirror-item{display:flex;align-items:center;justify-content:space-between;gap:14px;background:var(--panel); - border:1px solid var(--line);border-radius:12px;padding:16px 18px;flex-wrap:wrap} +.mirror-item{display:flex;align-items:center;justify-content:space-between;gap:14px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 18px;flex-wrap:wrap} .mirror-tag{font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 12px;white-space:nowrap} .mirror-tag.default{color:var(--ok);border-color:var(--ok)} .mirror-item code{color:var(--text)} .tips{text-align:center;color:var(--muted);font-size:14px;margin-top:16px} -.section .btn-ghost{display:inline-block;margin:0 auto} /* footer */ .footer{border-top:1px solid var(--line);padding:34px 22px} @@ -142,7 +129,11 @@ body::before{ .footer-links a{color:var(--muted)}.footer-links a:hover{color:var(--accent)} .footer-note{text-align:center;color:var(--dim);font-size:12.5px} -@media (max-width:760px){ +@media (max-width:840px){ + .hero-inner{grid-template-columns:1fr;text-align:center} + .hero-tag{margin-left:auto;margin-right:auto} + .hero-cta{justify-content:center} + .hero-note{justify-content:center} + .hero-globe{max-width:280px} .nav-links{display:none} - .map-title{letter-spacing:2px} } diff --git a/web/index.php b/web/index.php index b87f0c0..b43c757 100644 --- a/web/index.php +++ b/web/index.php @@ -1,14 +1,13 @@ @@ -55,8 +59,8 @@ $COLS = strlen($map[0]); ParlzPackageManager · 跨平台 C 语言包管理器 - - + + @@ -64,49 +68,54 @@ $COLS = strlen($map[0]); +
-
v · C11 · Windows / Linux / macOS
-

ParlzPackageManager (PMM)

-

一个用 C 语言从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 GitHub / GitLab / Gitea / Forgejo Release 装包。

-
- 快速开始 - 最新版 ↓ +
+
v · C11 · Windows / Linux / macOS
+

ParlzPackageManager (PMM)

+

一个用 C 语言从零编写的跨平台包管理器。像 apt 一样多用镜像源,也能直接从 GitHub / GitLab / Gitea / Forgejo Release 装包。

+ +
+ 双镜像 · 深圳 / 香港 实时可用 +
+
+
+ +
全球镜像 · 深圳 · 香港
- -
-
-
把世界装进一个包管理器
-
-'; - for ($c = 0; $c < $COLS; $c++) { - $ch = $map[$r][$c]; - if ($ch === '#') { - /* alternate '+'/'-' by position for a textured +/- map */ - $g = (($r + $c) % 2 === 0) ? '+' : '-'; - echo '' . $g . ''; - } else { - echo ''; - } - } - echo "
\n"; -} -?> +
+
+

服务 状态

+

实时监控 PMM 镜像服务器可用性与延迟(每 12 秒刷新)。

+
+
+
深圳
+
120.24.78.176
+
sz.pmm.parlz.com
+
检测中…
+
+
+
香港
+
38.76.190.153
+
pmm.parlz.com
+
检测中…
+
-
将光标移到大陆上,点亮整个世界
@@ -193,10 +202,14 @@ pmm -v Releases 回到顶部
- + + diff --git a/web/status.php b/web/status.php new file mode 100644 index 0000000..1534442 --- /dev/null +++ b/web/status.php @@ -0,0 +1,65 @@ + returns JSON for the homepage status panel. */ +header('Content-Type: application/json; charset=utf-8'); +header('Cache-Control: no-store'); + +$servers = [ + ['name'=>'深圳', 'ip'=>'120.24.78.176', 'url'=>'https://sz.pmm.parlz.com/mirror/packages/pmm.json'], + ['name'=>'香港', 'ip'=>'38.76.190.153', 'url'=>'https://pmm.parlz.com/mirror/packages/pmm.json'], +]; + +function probe($url) { + $t = microtime(true); + /* 1) PHP curl extension (preferred) */ + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_NOBODY => true, + CURLOPT_CONNECTTIMEOUT => 3, + CURLOPT_TIMEOUT => 6, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => 'pmm-status/1.0', + ]); + curl_exec($ch); + $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + $errno = curl_errno($ch); + curl_close($ch); + if ($errno === 0 && $code > 0) + return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)]; + } + /* 2) shell curl (pmm depends on the curl binary, so it is present) */ + if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) { + $cmd = 'curl -k -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 6 ' . escapeshellarg($url); + $out = trim((string)@shell_exec($cmd)); + $code = (int)$out; + if ($code > 0) + return ['online'=>$code < 500, 'code'=>$code, 'ms'=>(int)round((microtime(true)-$t)*1000)]; + } + /* 3) stream context fallback */ + $ctx = stream_context_create([ + 'http' => ['method'=>'HEAD','timeout'=>6,'follow_location'=>1,'ignore_errors'=>true], + 'ssl' => ['verify_peer'=>false,'verify_peer_name'=>false], + ]); + $fp = @fopen($url, 'r', false, $ctx); + $ms = (int)round((microtime(true) - $t) * 1000); + if ($fp) { fclose($fp); return ['online'=>true,'code'=>200,'ms'=>$ms]; } + return ['online'=>false,'code'=>0,'ms'=>$ms]; +} + +$out = []; +foreach ($servers as $s) { + $st = probe($s['url']); + $out[] = [ + 'name' => $s['name'], + 'ip' => $s['ip'], + 'online' => $st['online'], + 'code' => $st['code'], + 'ms' => $st['ms'], + ]; +} +echo json_encode($out, JSON_UNESCAPED_UNICODE);