small-package/luci-app-nekobox/htdocs/nekobox/ping.php

495 lines
16 KiB
PHP

<?php
ob_start();
include './cfg.php';
$translate = [
'United States' => '美国',
'China' => '中国',
'ISP' => '互联网服务提供商',
'Japan' => '日本',
'South Korea' => '韩国',
'Germany' => '德国',
'France' => '法国',
'United Kingdom' => '英国',
'Canada' => '加拿大',
'Australia' => '澳大利亚',
'Russia' => '俄罗斯',
'India' => '印度',
'Brazil' => '巴西',
'Netherlands' => '荷兰',
'Singapore' => '新加坡',
'Hong Kong' => '香港',
'Saudi Arabia' => '沙特阿拉伯',
'Turkey' => '土耳其',
'Italy' => '意大利',
'Spain' => '西班牙',
'Thailand' => '泰国',
'Malaysia' => '马来西亚',
'Indonesia' => '印度尼西亚',
'South Africa' => '南非',
'Mexico' => '墨西哥',
'Israel' => '以色列',
'Sweden' => '瑞典',
'Switzerland' => '瑞士',
'Norway' => '挪威',
'Denmark' => '丹麦',
'Belgium' => '比利时',
'Finland' => '芬兰',
'Poland' => '波兰',
'Austria' => '奥地利',
'Greece' => '希腊',
'Portugal' => '葡萄牙',
'Ireland' => '爱尔兰',
'New Zealand' => '新西兰',
'United Arab Emirates' => '阿拉伯联合酋长国',
'Argentina' => '阿根廷',
'Chile' => '智利',
'Colombia' => '哥伦比亚',
'Philippines' => '菲律宾',
'Vietnam' => '越南',
'Pakistan' => '巴基斯坦',
'Egypt' => '埃及',
'Nigeria' => '尼日利亚',
'Kenya' => '肯尼亚',
'Morocco' => '摩洛哥',
'Google' => '谷歌',
'Amazon' => '亚马逊',
'Microsoft' => '微软',
'Facebook' => '脸书',
'Apple' => '苹果',
'IBM' => 'IBM',
'Alibaba' => '阿里巴巴',
'Tencent' => '腾讯',
'Baidu' => '百度',
'Verizon' => '威瑞森',
'AT&T' => '美国电话电报公司',
'T-Mobile' => 'T-移动',
'Vodafone' => '沃达丰',
'China Telecom' => '中国电信',
'China Unicom' => '中国联通',
'China Mobile' => '中国移动',
'Chunghwa Telecom' => '中华电信',
'Amazon Web Services (AWS)' => '亚马逊网络服务 (AWS)',
'Google Cloud Platform (GCP)' => '谷歌云平台 (GCP)',
'Microsoft Azure' => '微软Azure',
'Oracle Cloud' => '甲骨文云',
'Alibaba Cloud' => '阿里云',
'Tencent Cloud' => '腾讯云',
'DigitalOcean' => '数字海洋',
'Linode' => '林诺德',
'OVHcloud' => 'OVH 云',
'Hetzner' => '赫兹纳',
'Vultr' => '沃尔特',
'OVH' => 'OVH',
'DreamHost' => '梦想主机',
'InMotion Hosting' => '动态主机',
'HostGator' => '主机鳄鱼',
'Bluehost' => '蓝主机',
'A2 Hosting' => 'A2主机',
'SiteGround' => '站点地',
'Liquid Web' => '液态网络',
'Kamatera' => '卡玛特拉',
'IONOS' => 'IONOS',
'InterServer' => '互联服务器',
'Hostwinds' => '主机之风',
'ScalaHosting' => '斯卡拉主机',
'GreenGeeks' => '绿色极客'
];
$lang = $_GET['lang'] ?? 'en';
?>
<!DOCTYPE html>
<html lang="<?php echo htmlspecialchars($lang); ?>">
<head>
<meta charset="utf-8">
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="dns-prefetch" href="//whois.pconline.com.cn">
<link rel="dns-prefetch" href="//forge.speedtest.cn">
<link rel="dns-prefetch" href="//api-ipv4.ip.sb">
<link rel="dns-prefetch" href="//api.ipify.org">
<link rel="dns-prefetch" href="//api.ttt.sh">
<link rel="dns-prefetch" href="//qqwry.api.skk.moe">
<link rel="dns-prefetch" href="//d.skk.moe">
<link rel="preconnect" href="https://forge.speedtest.cn">
<link rel="preconnect" href="https://whois.pconline.com.cn">
<link rel="preconnect" href="https://api-ipv4.ip.sb">
<link rel="preconnect" href="https://api.ipify.org">
<link rel="preconnect" href="https://api.ttt.sh">
<link rel="preconnect" href="https://qqwry.api.skk.moe">
<link rel="preconnect" href="https://d.skk.moe">
<style>
.img-con {
width: 65px;
height: 55px;
display: flex;
justify-content: center;
overflow: visible;
}
#flag {
width: auto;
height: auto;
max-width: 65px;
max-height: 55px;
object-fit: contain;
}
.status-icon {
width: 58px;
height: 58px;
object-fit: contain;
display: block;
}
.status-icons {
display: flex;
height: 55px;
margin-left: auto;
}
.site-icon {
display: flex;
justify-content: center;
height: 55px;
margin: 0 6px;
}
.mx-1 {
margin: 0 4px;
}
.site-icon[onclick*="github"] .status-icon {
width: 61px;
height: 59px;
}
.site-icon[onclick*="github"] {
width: 60px;
height: 57px;
display: flex;
justify-content: center;
}
.container-sm.container-bg.callout.border {
padding: 12px 15px;
min-height: 70px;
display: flex;
margin-bottom: 15px;
}
.row.align-items-center {
width: 100%;
margin: 0;
display: flex;
gap: 15px;
height: 55px; /
}
.col-3 {
height: 55px;
display: flex;
flex-direction: column;
justify-content: center;
}
.col.text-center {
position: static;
left: auto;
transform: none;
}
.container-sm .row .col-4 {
position: static !important;
order: 2 !important;
width: 100% !important;
padding-left: 54px !important;
margin-top: 5px !important;
text-align: left !important;
}
#ping-result {
font-weight: bold;
}
#d-ip {
color: #09B63F;
font-weight: 700 !important;
}
.info.small {
color: #ff69b4;
font-weight: 600;
white-space: nowrap;
}
.site-icon, .img-con {
cursor: pointer !important;
transition: all 0.2s ease !important;
position: relative !important;
user-select: none !important;
}
.site-icon:hover, .img-con:hover {
transform: translateY(-2px) !important;
}
.site-icon:active, .img-con:active {
transform: translateY(1px) !important;
opacity: 0.8 !important;
}
@media (max-width: 1280px) {
.site-icon[onclick*="baidu"],
.site-icon[onclick*="taobao"],
.site-icon[onclick*="google"],
.site-icon[onclick*="youtube"],
.site-icon[onclick*="github"] {
display: none !important;
}
}
</style>
<?php if (in_array($lang, ['zh-cn', 'en', 'auto'])): ?>
<div id="status-bar-component" class="container-sm container-bg callout border">
<div class="row align-items-center">
<div class="col-auto">
<div class="img-con">
<img src="./assets/neko/img/loading.svg" id="flag" title="国旗" onclick="IP.getIpipnetIP()">
</div>
</div>
<div class="col-3">
<p id="d-ip" class="ip-address mb-0">Checking...</p>
<p id="ipip" class="info small mb-0"></p>
</div>
<div class="col text-center">
<p id="ping-result" class="mb-0"></p>
</div>
<div class="col-auto ms-auto">
<div class="status-icons d-flex">
<div class="site-icon mx-1" onclick="pingHost('baidu', 'Baidu')">
<img src="./assets/neko/img/site_icon_01.png" id="baidu-normal" class="status-icon" style="display: none;">
<img src="./assets/neko/img/site_icon1_01.png" id="baidu-gray" class="status-icon">
</div>
<div class="site-icon mx-1" onclick="pingHost('taobao', '淘宝')">
<img src="./assets/neko/img/site_icon_02.png" id="taobao-normal" class="status-icon" style="display: none;">
<img src="./assets/neko/img/site_icon1_02.png" id="taobao-gray" class="status-icon">
</div>
<div class="site-icon mx-1" onclick="pingHost('google', 'Google')">
<img src="./assets/neko/img/site_icon_03.png" id="google-normal" class="status-icon" style="display: none;">
<img src="./assets/neko/img/site_icon1_03.png" id="google-gray" class="status-icon">
</div>
<div class="site-icon mx-1" onclick="pingHost('youtube', 'YouTube')">
<img src="./assets/neko/img/site_icon_04.png" id="youtube-normal" class="status-icon" style="display: none;">
<img src="./assets/neko/img/site_icon1_04.png" id="youtube-gray" class="status-icon">
</div>
<div class="site-icon mx-1" onclick="pingHost('github', 'GitHub')">
<img src="./assets/neko/img/site_icon_05.png" id="github-normal" title="测试 GitHub 延迟" class="status-icon" style="display: none;">
<img src="./assets/neko/img/site_icon1_05.png" id="github-gray" class="status-icon">
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<script src="./assets/neko/js/jquery.min.js"></script>
<script type="text/javascript">
const _IMG = './assets/neko/';
const translate = <?php echo json_encode($translate, JSON_UNESCAPED_UNICODE); ?>;
let cachedIP = null;
let cachedInfo = null;
let random = parseInt(Math.random() * 100000000);
const checkSiteStatus = {
sites: {
baidu: 'https://www.baidu.com',
taobao: 'https://www.taobao.com',
google: 'https://www.google.com',
youtube: 'https://www.youtube.com',
github: 'https://www.github.com'
},
check: async function() {
for (let [site, url] of Object.entries(this.sites)) {
try {
const response = await fetch(url, {
mode: 'no-cors',
cache: 'no-cache'
});
document.getElementById(`${site}-normal`).style.display = 'inline';
document.getElementById(`${site}-gray`).style.display = 'none';
} catch (error) {
document.getElementById(`${site}-normal`).style.display = 'none';
document.getElementById(`${site}-gray`).style.display = 'inline';
}
}
}
};
async function pingHost(site, siteName) {
const url = checkSiteStatus.sites[site];
const resultElement = document.getElementById('ping-result');
try {
resultElement.innerHTML = `<span style="font-size: 22px">正在测试 ${siteName} 的连接延迟...`;
resultElement.style.color = '#87CEFA';
const startTime = performance.now();
await fetch(url, {
mode: 'no-cors',
cache: 'no-cache'
});
const endTime = performance.now();
const pingTime = Math.round(endTime - startTime);
resultElement.innerHTML = `<span style="font-size: 22px">${siteName} 连接延迟: ${pingTime}ms</span>`;
if(pingTime <= 100) {
resultElement.style.color = '#09B63F';
} else if(pingTime <= 200) {
resultElement.style.color = '#FFA500';
} else {
resultElement.style.color = '#ff6b6b';
}
} catch (error) {
resultElement.innerHTML = `<span style="font-size: 22px">${siteName} 连接超时`;
resultElement.style.color = '#ff6b6b';
}
}
let IP = {
isRefreshing: false,
fetchIP: async () => {
try {
const [ipifyResp, ipsbResp, chinaIpResp] = await Promise.all([
IP.get('https://api.ipify.org?format=json', 'json'),
IP.get('https://api-ipv4.ip.sb/geoip', 'json'),
IP.get('https://myip.ipip.net', 'text')
]);
const ipData = ipifyResp.data.ip || ipsbResp.data.ip;
cachedIP = ipData;
document.getElementById('d-ip').innerHTML = ipData;
return ipData;
} catch (error) {
console.error("Error fetching IP:", error);
throw error;
}
},
get: (url, type) =>
fetch(url, {
method: 'GET',
cache: 'no-store'
}).then((resp) => {
if (type === 'text')
return Promise.all([resp.ok, resp.status, resp.text(), resp.headers]);
else
return Promise.all([resp.ok, resp.status, resp.json(), resp.headers]);
}).then(([ok, status, data, headers]) => {
if (ok) {
return { ok, status, data, headers };
} else {
throw new Error(JSON.stringify(data.error));
}
}).catch(error => {
console.error("Error fetching data:", error);
throw error;
}),
Ipip: async (ip, elID) => {
try {
const [ipsbResp, chinaIpResp] = await Promise.all([
IP.get(`https://api.ip.sb/geoip/${ip}`, 'json'),
IP.get(`https://myip.ipip.net`, 'text')
]);
cachedIP = ip;
cachedInfo = ipsbResp.data;
let chinaIpInfo = null;
try {
if(chinaIpResp.data) {
chinaIpInfo = chinaIpResp.data;
}
} catch(e) {
console.error("Error parsing China IP info:", e);
}
const mergedData = {
...ipsbResp.data,
// chinaIpInfo: chinaIpInfo
};
IP.updateUI(mergedData, elID);
} catch (error) {
console.error("Error in Ipip function:", error);
document.getElementById(elID).innerHTML = "获取IP信息失败";
}
},
updateUI: (data, elID) => {
try {
if (!data || !data.country_code) {
document.getElementById('d-ip').innerHTML = "无法获取IP信息";
return;
}
let country = translate[data.country] || data.country || "未知";
let isp = translate[data.isp] || data.isp || "";
let asnOrganization = translate[data.asn_organization] || data.asn_organization || "";
if (data.country === 'Taiwan') {
country = (navigator.language === 'en') ? 'China Taiwan' : '中国台湾';
}
const countryAbbr = data.country_code.toLowerCase();
const isChinaIP = ['cn', 'hk', 'mo', 'tw'].includes(countryAbbr);
let firstLineInfo = `<div style="white-space: nowrap;">`;
firstLineInfo += cachedIP + ' ';
let ipLocation = isChinaIP ?
'<span style="color: #00FF00;">[国内 IP]</span> ' :
'<span style="color: #FF0000;">[境外 IP]</span> ';
// firstLineInfo += ipLocation;
if (data.chinaIpInfo) {
firstLineInfo += `[${data.chinaIpInfo}]`;
}
firstLineInfo += `</div>`;
document.getElementById('d-ip').innerHTML = firstLineInfo;
document.getElementById('ipip').innerHTML = `${country} ${isp} ${asnOrganization}`;
document.getElementById('ipip').style.color = '#FF00FF';
$("#flag").attr("src", _IMG + "flags/" + countryAbbr + ".png");
} catch (error) {
console.error("Error in updateUI:", error);
document.getElementById('d-ip').innerHTML = "更新IP信息显示失败";
}
},
getIpipnetIP: async () => {
if(IP.isRefreshing) return;
try {
IP.isRefreshing = true;
document.getElementById('d-ip').innerHTML = "Checking...";
document.getElementById('ipip').innerHTML = "Loading...";
$("#flag").attr("src", _IMG + "img/loading.svg");
const ip = await IP.fetchIP();
await IP.Ipip(ip, 'ipip');
} catch (error) {
console.error("Error in getIpipnetIP function:", error);
document.getElementById('ipip').innerHTML = "获取IP信息失败";
} finally {
IP.isRefreshing = false;
}
}
};
IP.getIpipnetIP();
checkSiteStatus.check();
setInterval(() => checkSiteStatus.check(), 30000);
setInterval(IP.getIpipnetIP, 180000);
</script>
</body>
</html>