From 745cc6ffc809e282b2be07d094a83a2092fda4eb Mon Sep 17 00:00:00 2001 From: JGZYES Date: Wed, 2 Sep 2026 19:41:11 +0800 Subject: [PATCH] web: split into home + 4 pages (features/install/download/servers-status); detailed multi-endpoint status API (server header/latency/timestamp); shared _common.php layout --- web/_common.php | 68 ++++++++++++++++ web/assets/main.js | 84 ++++++++++++-------- web/assets/style.css | 37 +++++++++ web/download.php | 38 +++++++++ web/features.php | 24 ++++++ web/index.php | 181 +++++++------------------------------------ web/install.php | 52 +++++++++++++ web/servers.php | 26 +++++++ web/status.php | 104 +++++++++++++------------ 9 files changed, 382 insertions(+), 232 deletions(-) create mode 100644 web/_common.php create mode 100644 web/download.php create mode 100644 web/features.php create mode 100644 web/install.php create mode 100644 web/servers.php diff --git a/web/_common.php b/web/_common.php new file mode 100644 index 0000000..61cb627 --- /dev/null +++ b/web/_common.php @@ -0,0 +1,68 @@ + ['index.php', '首页'], + 'features' => ['features.php', '特性'], + 'install' => ['install.php', '安装'], + 'download' => ['download.php', '下载'], + 'status' => ['servers.php', '服务状态'], + ]; + $html = ''; + foreach ($items as $key => [$href, $label]) { + $cls = $key === $active ? ' class="active"' : ''; + $html .= '
  • ' . $label . '
  • '; + } + return $html; +} + +function pmm_header(string $active, string $title): void { + $t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); + echo '' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . $t . ' · ParlzPackageManager' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n" . +'' . "\n"; +} + +function pmm_page_open(string $kicker, string $title): void { + $k = htmlspecialchars($kicker, ENT_QUOTES, 'UTF-8'); + $t = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); + echo '
    ' . "\n" . + '
    ' . $k . '
    ' . "\n" . + '

    ' . $t . '

    ' . "\n" . + '
    ' . "\n"; +} + +function pmm_footer(): void { + echo '' . "\n" . + '' . "\n" . + '' . "\n" . ''; +} diff --git a/web/assets/main.js b/web/assets/main.js index e6dfb15..7e13b27 100644 --- a/web/assets/main.js +++ b/web/assets/main.js @@ -1,4 +1,4 @@ -/* PMM site — rotating dotted Earth globe + live status + tabs + copy */ +/* PMM site — rotating dotted Earth globe + detailed live status + tabs + copy */ (function () { "use strict"; @@ -48,14 +48,9 @@ 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 - }; + 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 } @@ -74,14 +69,12 @@ window.addEventListener("resize", resize); var theta = 0, rafId = null; - var ACC = "#d8d8d8", OKC = "#ffffff"; /* grayscale palette */ 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(255,255,255,0.045)"; @@ -90,19 +83,14 @@ ctx.lineWidth = 1; ctx.stroke(); - // faint equator + meridian rings for a globe feel ctx.strokeStyle = "rgba(255,255,255,0.11)"; 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 }; + return { x: cos(lat) * sin(r), y: sin(lat), z: cos(lat) * cos(r) }; } - // 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++) { @@ -113,7 +101,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; @@ -121,7 +108,7 @@ 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.fillStyle = "#ffffff"; ctx.fill(); ctx.beginPath(); ctx.arc(sx, sy, 4 + (1 - pulse) * 4, 0, 2 * Math.PI); @@ -133,7 +120,6 @@ rafId = requestAnimationFrame(draw); } - // only animate while visible if ("IntersectionObserver" in window) { var io = new IntersectionObserver(function (en) { en.forEach(function (e) { @@ -147,24 +133,56 @@ } } - /* ---------- Live mirror status ---------- */ + /* ---------- Detailed live mirror status (servers.php) ---------- */ + function esc(s) { + return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { + return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]; + }); + } + function ms(v) { return (v == null ? "—" : v + " ms"); } + + function renderStatus(data) { + if (!Array.isArray(data)) return; + var detail = document.getElementById("svc-detail"); + var html = ""; + data.forEach(function (s, i) { + var dot = document.getElementById("dot-" + i); + var meta = document.getElementById("meta-" + i); + if (dot) dot.className = "status-dot " + (s.online ? "up" : "down"); + if (meta) + meta.innerHTML = s.online + ? "在线 · 平均 " + esc(s.avg_ms) + " ms" + : "离线 / 不可达"; + html += '
    ' + + '

    ' + + esc(s.name) + ' · ' + esc(s.region) + '

    ' + + '
    ' + esc(s.host) + ' · 检测时间 ' + esc(s.checked_at) + '
    ' + + ''; + (s.checks || []).forEach(function (c) { + var r = c.result || {}; + html += '' + + '' + + '' + + '' + + ''; + }); + html += '
    检测项状态HTTP延迟Server
    ' + esc(c.item) + '
    ' + esc(c.url) + '
    ' + (r.ok ? "正常" : "异常") + '' + (r.code || "—") + '' + ms(r.ms) + '' + esc(r.server || "—") + '
    '; + }); + if (detail) detail.innerHTML = html; + var upd = document.getElementById("status-updated"); + if (upd && data[0] && data[0].checked_at) upd.textContent = "最近检测:" + data[0].checked_at + "(UTC)"; + } + 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 - : "离线 / 不可达"; - }); - }) + .then(renderStatus) .catch(function () {}); } - loadStatus(); - setInterval(loadStatus, 12000); + var refreshBtn = document.getElementById("status-refresh"); + if (refreshBtn) refreshBtn.addEventListener("click", loadStatus); + if (document.getElementById("svc-detail")) { + loadStatus(); + setInterval(loadStatus, 15000); + } })(); diff --git a/web/assets/style.css b/web/assets/style.css index 5a25a69..49fffe5 100644 --- a/web/assets/style.css +++ b/web/assets/style.css @@ -136,3 +136,40 @@ code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Co .hero-globe{max-width:280px} .nav-links{display:none} } + +/* ---------- multi-page layout ---------- */ +.nav-links a.active{color:#fff;font-weight:700} +.page-hero{border-bottom:1px solid var(--line);background:var(--bg2)} +.page-hero .wrap{padding:46px 22px 34px} +.page-kicker{font-size:12px;letter-spacing:4px;color:var(--dim);text-transform:uppercase;margin-bottom:10px} +.page-hero h1{font-size:clamp(28px,4vw,40px);font-weight:800;letter-spacing:-.5px} + +/* home entry grid */ +.entry-section{padding-top:34px} +.entry-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px} +.entry-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:24px; + display:flex;flex-direction:column;gap:8px;transition:transform .2s,border-color .2s} +.entry-card:hover{transform:translateY(-3px);border-color:#fff} +.entry-icon{font-size:22px;color:#fff} +.entry-title{font-size:17px;font-weight:800} +.entry-desc{font-size:13.5px;color:var(--muted)} + +/* status page */ +.status-toolbar{display:flex;align-items:center;justify-content:space-between;gap:14px; + max-width:760px;margin:0 auto 18px;flex-wrap:wrap} +.status-updated{font-size:13px;color:var(--dim);font-family:ui-monospace,monospace} +.svc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;max-width:760px;margin:0 auto} +.svc-detail-wrap{max-width:760px;margin:34px auto 0;display:grid;gap:22px} +.svc-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px} +.svc-card h3{display:flex;align-items:center;gap:10px;font-size:16px;margin-bottom:4px} +.svc-host{font-family:ui-monospace,monospace;font-size:12.5px;color:var(--dim);margin-bottom:14px} +.svc-table{width:100%;border-collapse:collapse;font-size:13.5px} +.svc-table th{ text-align:left;color:var(--dim);font-weight:600;padding:8px 10px;border-bottom:1px solid var(--line)} +.svc-table td{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top;color:var(--muted)} +.svc-table tr:last-child td{border-bottom:0} +.svc-table code{font-size:12px;color:#bdbdbd;word-break:break-all} +.svc-badge{display:inline-block;font-size:11.5px;border-radius:999px;padding:2px 10px;border:1px solid var(--line);color:var(--muted);white-space:nowrap} +.svc-badge.up{color:#fff;border-color:#fff} +.svc-badge.down{color:#bbb;border-style:dashed} +.svc-ok{color:#fff;font-weight:700} +.svc-bad{color:#9a9a9a} diff --git a/web/download.php b/web/download.php new file mode 100644 index 0000000..9b754f3 --- /dev/null +++ b/web/download.php @@ -0,0 +1,38 @@ + + +
    +
    +

    可从官方镜像站或 GitHub Release 获取。镜像地址不变。

    + +

    Linux 也可 curl -sSL https://pmm.parlz.com/install.sh | bash.pdm 包可直接 pmm install pmm 安装。

    + +

    镜像

    +

    PMM 默认镜像(apt 式,按优先级回退),地址与 mirror.ini 一致。

    +
    +
    sz · 默认https://sz.pmm.parlz.com/mirror/packages
    +
    main · 备份https://pmm.parlz.com/mirror/packages
    +
    +
    浏览镜像目录 ↗
    +

    全部历史版本的 .pdm 都在镜像 mirror/packages/pmm/ 下。

    +
    +
    + + diff --git a/web/features.php b/web/features.php new file mode 100644 index 0000000..4317f20 --- /dev/null +++ b/web/features.php @@ -0,0 +1,24 @@ + + +
    +
    +
    +

    apt 式多镜像源

    支持 mirror.ini / mirror.conf,按优先级自动回退;默认自带 sz(深圳)与 main(香港)两个镜像,缺失时自动生成。

    +

    Git Release 安装

    pmm install --git repo 自动识别 GitHub / GitLab / Gitea / Forgejo 及私有部署;支持 repo@tag / -b branch 安装指定版本。

    +

    .pdm 打包格式

    pmm pack <dir> 生成含 control.tar.gz / data.tar.gz / sha256sums 的归档,思路同 .deb。

    +

    依赖解析

    pdm-control 声明 Depends:,registry 包声明 "depends": [...];安装时自动递归解析并按版本约束(==/≥/≤)安装,带防环保护。

    +

    并发下载 + 断点续传

    大文件(≥8MB)自动 4 路 Range 分片并行下载、逐片续传、拼接后校验;失败自动回退单流。进度条含已用/剩余时间与实时速度。

    +

    哈希校验

    内置 SHA-256 与 SHA-1(FIPS 180-4),下载后强校验;pmm verify <file> 随时验证任意安装包。

    +

    本地包安装

    pmm install -dpkg x.deb(Linux)、pmm install -msi x.msi(Windows),也支持 .rpm/.zip/.tar.gz/.exe/.dmg 等,直接装文件或 URL。

    +

    搜索与信息

    pmm search <关键词> 发现镜像里的包;pmm info <pkg> 查看版本与平台变体;pmm list 列出已装包。

    +

    缓存管理

    下载缓存统一放在 <base>/cachepmm cache clean 一键清空,--no-cache 强制重新下载。

    +
    +
    +
    + + diff --git a/web/index.php b/web/index.php index c77dc5b..39f056b 100644 --- a/web/index.php +++ b/web/index.php @@ -1,13 +1,9 @@ - - - - - -ParlzPackageManager · 跨平台 C 语言包管理器 - - - - - - - -
    -
    v · C11 · Windows / Linux / macOS
    +
    v · C11 · Windows / Linux / macOS

    ParlzPackageManager (PMM)

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

    -
    - 双镜像 · 深圳 / 香港 实时可用 + 快速安装 + 下载 v
    +
    双镜像 · 深圳 / 香港 实时在线
    @@ -103,127 +72,37 @@ for ($r = 0; $r < $ROWS; $r++)
    -
    +
    -

    服务 状态

    -

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

    -
    -
    -
    深圳
    -
    sz.pmm.parlz.com
    -
    https://sz.pmm.parlz.com/mirror/packages
    -
    检测中…
    -
    -
    -
    香港
    -
    pmm.parlz.com
    -
    https://pmm.parlz.com/mirror/packages
    -
    检测中…
    -
    -
    -
    -
    - -
    -
    -

    核心 特性

    -
    -

    apt 式多镜像源

    支持 mirror.ini / mirror.conf,按优先级自动回退;默认带 sz(默认)与 main 两个镜像。

    -

    Git Release 安装

    pmm install --git repo 自动识别 GitHub / GitLab / Gitea / Forgejo 及私有部署,按平台挑资产。

    -

    .pdm 打包格式

    pmm pack <dir> 生成含 control.tar.gz / data.tar.gz / sha256sums 的归档,思路同 .deb。

    -

    哈希校验

    内置 SHA-256 与 SHA-1 实现,安装前强校验下载文件完整性,防篡改。

    -

    版本选择

    支持 pmm install pkg==1.2.3>=<=>< 等 pip 式约束。

    -

    进度条 & 注册表

    ASCII 下载进度条;Windows 下为已装软件写入注册表卸载项,可用“应用和功能”卸载。

    -
    -
    -
    - -
    -
    -

    安装 PMM

    -

    下载对应平台二进制,一条命令搞定,完成后自动加入 PATH。

    -
    - - - -
    -
    -
    # ZIP 免安装版(解压即用,pmm.exe 在压缩包内)
    -curl -L -o pmm.zip https://pmm.parlz.com/downloads/pmm--windows-amd64.zip
    -# 解压后把 pmm.exe 所在目录加入 PATH,然后:
    -pmm -v
    - -
    -
    -
    # 方式一:sh 一键安装(自动下载二进制 + 加 PATH)
    -curl -sSL https://pmm.parlz.com/install.sh | bash
    -
    -# 方式二:.deb 安装
    -curl -L -o pmm.deb https://pmm.parlz.com/downloads/pmm__amd64.deb
    -sudo apt install ./pmm.deb
    -pmm -v
    - -
    -
    -
    # sh 一键安装
    -curl -sSL https://pmm.parlz.com/install.sh | bash
    -pmm -v
    - -
    -
    -
    - -
    -
    -
    -
    -

    镜像

    -

    PMM 默认镜像(apt 式,按优先级回退),地址与 mirror.ini 一致。

    -
    -
    sz · 默认https://sz.pmm.parlz.com/mirror/packages
    -
    main · 备份https://pmm.parlz.com/mirror/packages
    -
    - 浏览镜像目录 ↗ -

    快速开始:
    pmm install nodejs — Node.js
    pmm install php — PHP
    pmm install pmm — 升级 PMM 自身

    -
    -
    - -
    -
    - - - -
    -
    +

    pmm install nodejs · pmm install php · pmm install pmm(升级自身)

    - - - + diff --git a/web/install.php b/web/install.php new file mode 100644 index 0000000..9e2c3bc --- /dev/null +++ b/web/install.php @@ -0,0 +1,52 @@ + + +
    +
    +

    下载对应平台二进制,一条命令搞定,完成后自动加入 PATH。

    +
    + + + +
    +
    +
    # ZIP 免安装版(解压即用,pmm.exe 在压缩包内)
    +curl -L -o pmm.zip https://pmm.parlz.com/downloads/pmm--windows-amd64.zip
    +# 解压后把 pmm.exe 所在目录加入 PATH,然后:
    +pmm -v
    + +
    +
    +
    # 方式一:sh 一键安装(自动下载二进制 + 加 PATH)
    +curl -sSL https://pmm.parlz.com/install.sh | bash
    +
    +# 方式二:.deb 安装
    +curl -L -o pmm.deb https://pmm.parlz.com/downloads/pmm__amd64.deb
    +sudo apt install ./pmm.deb
    +pmm -v
    + +
    +
    +
    # sh 一键安装
    +curl -sSL https://pmm.parlz.com/install.sh | bash
    +pmm -v
    + +
    + +

    装好之后

    +
    +
    pmm search node          # 在镜像里搜包
    +pmm install nodejs       # 安装(自动解析依赖)
    +pmm install nodejs==24.20.0   # 指定版本
    +pmm install --git owner/repo@v1.2.3   # 从 git release 安装指定 tag
    +pmm list                 # 已装列表
    +pmm remove nodejs        # 卸载
    +
    +
    +
    + + diff --git a/web/servers.php b/web/servers.php new file mode 100644 index 0000000..5437925 --- /dev/null +++ b/web/servers.php @@ -0,0 +1,26 @@ + + +
    +
    +

    实时监控 PMM 双镜像(深圳 / 香港)的注册表、下载服务与 Web 浏览三项能力,每 15 秒自动刷新。不暴露任何服务器 IP。

    + +
    + 检测中… + +
    + +
    +
    深圳
    sz.pmm.parlz.com
    https://sz.pmm.parlz.com/mirror/packages
    检测中…
    +
    香港
    pmm.parlz.com
    https://pmm.parlz.com/mirror/packages
    检测中…
    +
    + +
    +
    +
    + + diff --git a/web/status.php b/web/status.php index 59cdbe4..ec562e9 100644 --- a/web/status.php +++ b/web/status.php @@ -1,64 +1,72 @@ returns JSON for the homepage status panel. */ +/* Detailed status probe for the PMM mirror servers. + * For each mirror we probe three endpoints (registry index / package download / + * web dir listing) and report HTTP code, latency, server software and a + * timestamp. Output is JSON for servers.php. No server IPs are exposed. */ +define('PMM_SITE', 1); +require __DIR__ . '/_common.php'; header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); $servers = [ - ['name'=>'深圳', 'url'=>'https://sz.pmm.parlz.com/mirror/packages/pmm.json'], - ['name'=>'香港', 'url'=>'https://pmm.parlz.com/mirror/packages/pmm.json'], + ['name' => '深圳', 'region' => 'CN · 深圳', 'host' => 'sz.pmm.parlz.com'], + ['name' => '香港', 'region' => 'HK · 香港', 'host' => 'pmm.parlz.com'], ]; -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)]; +/* Probe one URL with shell curl: returns [code, ms, server, ctype]. */ +function probe_url(string $url): array { + $cmd = 'curl -k -s -o /dev/null -D - --max-time 8 ' + . '-w "__PMM_PROBE__%{http_code} %{time_total}" ' . escapeshellarg($url); + $out = (string)@shell_exec($cmd); + $code = 0; $ms = 0; $server = ''; $ctype = ''; + if ($out !== '') { + /* headers before the marker, "code ms" after it */ + $pos = strrpos($out, '__PMM_PROBE__'); + if ($pos !== false) { + $meta = trim(substr($out, $pos + strlen('__PMM_PROBE__'))); + $parts = preg_split('/\s+/', $meta); + if (isset($parts[0])) $code = (int)$parts[0]; + if (isset($parts[1])) $ms = (int)round(((float)$parts[1]) * 1000); + $head = substr($out, 0, $pos); + if (preg_match('/^Server:\s*(.+)$/mi', $head, $m)) $server = trim($m[1]); + if (preg_match('/^Content-Type:\s*(.+)$/mi', $head, $m)) $ctype = trim($m[1]); + } } - /* 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]; + return [$code, $ms, $server, $ctype]; +} + +function probe_check(string $url): array { + [$code, $ms, $server, $ctype] = probe_url($url); + return [ + 'ok' => ($code >= 200 && $code < 500 && $code !== 0), + 'code' => $code, + 'ms' => $ms, + 'server' => $server, + 'ctype' => $ctype, + ]; } $out = []; foreach ($servers as $s) { - $st = probe($s['url']); + $h = $s['host']; + $reg = probe_check("https://{$h}/mirror/packages/pmm.json"); + $dl = probe_check("https://{$h}/mirror/packages/pmm/" . PMM_VERSION . "-linux-amd64.pdm"); + $web = probe_check("https://{$h}/mirror/"); + $avg = 0; $n = 0; + foreach ([$reg, $dl, $web] as $c) if ($c['ok']) { $avg += $c['ms']; $n++; } + $avg = $n ? (int)round($avg / $n) : 0; $out[] = [ - 'name' => $s['name'], - 'online' => $st['online'], - 'code' => $st['code'], - 'ms' => $st['ms'], + 'name' => $s['name'], + 'region' => $s['region'], + 'host' => $h, + 'online' => $reg['ok'], + 'avg_ms' => $avg, + 'checks' => [ + ['item' => '注册表 (packages API)', 'url' => "https://{$h}/mirror/packages/pmm.json", 'result' => $reg], + ['item' => '下载服务 (.pdm 分发)', 'url' => "https://{$h}/mirror/packages/pmm/" . PMM_VERSION . "-linux-amd64.pdm", 'result' => $dl], + ['item' => 'Web 目录浏览', 'url' => "https://{$h}/mirror/", 'result' => $web], + ], + 'checked_at' => gmdate('Y-m-d\TH:i:s\Z'), ]; } echo json_encode($out, JSON_UNESCAPED_UNICODE);