mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-11 02:44:57 +08:00
1596 lines
74 KiB
HTML
1596 lines
74 KiB
HTML
<%
|
|
local fs = require "luci.openclash"
|
|
local plugin_version = fs.oc_version()
|
|
local version_tab = (self and self.version_tab) or false
|
|
%>
|
|
<script src="/luci-static/resources/openclash/js/common.js?v=<%=plugin_version%>"></script>
|
|
<script src="/luci-static/resources/openclash/js/oc-icons.js?v=<%=plugin_version%>"></script>
|
|
<div class="oc">
|
|
<div id="selectPopup" class="select-popup<%= version_tab and ' select-popup-tab' or ' hidden' %>">
|
|
<div class="select-popup-header">
|
|
<div class="config-editor-title">
|
|
<%:Check Update%>
|
|
</div>
|
|
<div class="config-editor-actions">
|
|
<button type="button" class="icon-btn" onclick="refreshAllData()" title="<%:Refresh%>">
|
|
<svg width="14" height="14"><use href="#oc-icon-refresh"/></svg>
|
|
</button>
|
|
<button type="button" class="icon-btn<%= version_tab and ' oc-hidden' or '' %>" onclick="closeSelectPopup()" title="<%:Close%>">
|
|
<svg width="14" height="14"><use href="#oc-icon-close"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="select-config-section">
|
|
<div class="config-grid">
|
|
<div class="config-item">
|
|
<div class="oc-option-title">
|
|
<span class="oc-option-text"><%:CPU Arch%></span>
|
|
</div>
|
|
<span class="ver-value" id="CPU_ARCH_VALUE"><%:Collecting data...%></span>
|
|
</div>
|
|
<div class="config-item">
|
|
<div class="oc-option-title"><span class="oc-option-text"><%:Compiled Version%><b> *</b></span></div>
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class oc-arch-select" id="CORE_VERSION" onchange="onArchChange()">
|
|
<option value="linux-386"><%:linux-386%></option>
|
|
<option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
|
|
<option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
|
|
<option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
|
|
<option value="linux-armv5"><%:linux-armv5%></option>
|
|
<option value="linux-armv6"><%:linux-armv6%></option>
|
|
<option value="linux-armv7"><%:linux-armv7%></option>
|
|
<option value="linux-arm64"><%:linux-arm64(armv8)%></option>
|
|
<option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
|
|
<option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
|
|
<option value="linux-riscv64"><%:linux-riscv64%></option>
|
|
<option value="linux-s390x"><%:linux-s390x%></option>
|
|
<option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
|
|
<option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
|
|
<option value="linux-mips64"><%:linux-mips64%></option>
|
|
<option value="linux-mips64le"><%:linux-mips64le%></option>
|
|
<option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
|
|
<option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
|
|
<option value="0"><%:Not Set%></option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="config-item">
|
|
<div class="oc-option-title"><span class="oc-option-text"><%:Release Branch%></span></div>
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class" id="RELEASE_BRANCH" onchange="onBranchChange()">
|
|
<option value="master">Master</option>
|
|
<option value="dev">Developer</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="config-item">
|
|
<div class="oc-option-title">
|
|
<span class="oc-option-text"><%:Smart Core%></span>
|
|
<a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" class="help-link" title="<%:View core infos that support smart group%>">
|
|
<svg width="12" height="12"><use href="#oc-icon-help"/></svg>
|
|
</a>
|
|
</div>
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class" id="SMART_ENABLE" onchange="onSmartChange()">
|
|
<option value="0"><%:Disabled%></option>
|
|
<option value="1"><%:Enabled%></option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="version-card">
|
|
<div class="version-row">
|
|
<div class="config-item">
|
|
<span class="oc-option-title"><span class="oc-option-text"><%:Plugin Version%></span></span>
|
|
<span class="ver-value" id="installed-plugin">--</span>
|
|
</div>
|
|
<div class="config-item">
|
|
<span class="oc-option-title"><span class="oc-option-text"><%:Select Version%></span></span>
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class" id="select-plugin-ver" onchange="forceRefresh=true;refreshAddrInfo({cancel:true,user:true})">
|
|
<option value="__latest__"><%:Latest%></option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="config-item">
|
|
<span class="oc-option-title"><span class="oc-option-text"><%:Core Version%></span></span>
|
|
<span class="ver-value" id="installed-core">--</span>
|
|
</div>
|
|
<div class="config-item">
|
|
<span class="oc-option-title"><span class="oc-option-text"><%:Select Version%></span></span>
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class" id="select-core-ver" onchange="forceRefresh=true;refreshAddrInfo({cancel:true,user:true})">
|
|
<option value="__latest__"><%:Latest%></option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="ver-hint" id="version-hint"></div>
|
|
|
|
<div class="select-popup-body" id="addr-list-body">
|
|
<div class="addr-list-scroll">
|
|
<div class="select-option addr-header">
|
|
<span class="addr-header-label"><span class="addr-header-index">#</span></span>
|
|
<span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-link-tabler"/></svg></span><span><%:Address%></span></span>
|
|
<span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-speedtest"/></svg></span><span><%:Latency%></span></span>
|
|
<span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-package"/></svg></span><span><%:Plugin%></span></span>
|
|
<span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-cpu"/></svg></span><span><%:Core%></span>
|
|
<div class="help-link" title="<%:Core path:%>/etc/openclash/core/clash_meta">
|
|
<svg width="12" height="12"><use href="#oc-icon-help"/></svg>
|
|
</div>
|
|
</span>
|
|
<span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-zap"/></svg></span><span><%:Proxy%></span>
|
|
<div class="help-link" title="<%:Proxy The GitHub Address In The Config And OpenClash With Proxy(CDN) To Prevent File Download Failed.%>">
|
|
<svg width="12" height="12"><use href="#oc-icon-help"/></svg>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
<div class="select-option addr-row" data-value="https://raw.githubusercontent.com/">
|
|
<span class="addr-row-index"></span>
|
|
<span class="addr-row-url">https://raw.githubusercontent.com/</span>
|
|
<span class="addr-status">--</span>
|
|
<span class="ver-text ver-plugin">--</span>
|
|
<span class="ver-text ver-core">--</span>
|
|
<span class="addr-proxy-static"><%:RAW%></span>
|
|
</div>
|
|
<div class="select-option addr-row" data-value="https://fastly.jsdelivr.net/">
|
|
<span class="addr-row-index"></span>
|
|
<span class="addr-row-url">https://fastly.jsdelivr.net/</span>
|
|
<span class="addr-status">--</span>
|
|
<span class="ver-text ver-plugin">--</span>
|
|
<span class="ver-text ver-core">--</span>
|
|
</div>
|
|
<div class="select-option addr-row" data-value="https://testingcf.jsdelivr.net/">
|
|
<span class="addr-row-index"></span>
|
|
<span class="addr-row-url">https://testingcf.jsdelivr.net/</span>
|
|
<span class="addr-status">--</span>
|
|
<span class="ver-text ver-plugin">--</span>
|
|
<span class="ver-text ver-core">--</span>
|
|
</div>
|
|
<div class="select-option addr-row" data-value="https://cdn.jsdelivr.net/">
|
|
<span class="addr-row-index"></span>
|
|
<span class="addr-row-url">https://cdn.jsdelivr.net/</span>
|
|
<span class="addr-status">--</span>
|
|
<span class="ver-text ver-plugin">--</span>
|
|
<span class="ver-text ver-core">--</span>
|
|
</div>
|
|
<div class="select-option" data-value="custom" id="custom-addr-option">
|
|
<span><%:Custom Your CDN URL%></span>
|
|
<span class="oc-flex-center" style="height:100%;">
|
|
<svg width="18" height="18"><use href="#oc-icon-chevron-down"/></svg>
|
|
</span>
|
|
</div>
|
|
<input type="text" id="customOptionInput" class="custom-option-input oc-hidden" value="" placeholder="<%:Type CDN URL, Format Like%> https://ghfast.top/">
|
|
<div class="select-option oc-hidden" id="addCustomOption"><%:Add%></div>
|
|
</div>
|
|
<div class="addr-list-loading oc-hidden" id="addr-list-loading">
|
|
<div class="addr-list-spinner"></div>
|
|
</div>
|
|
</div>
|
|
<div class="select-popup-footer" id="select-popup-footer">
|
|
<div class="form-select-wrapper">
|
|
<select class="select-class" id="BACKUP_SELECT">
|
|
<option value="backup"><%:Backup File%></option>
|
|
<option value="backup_ex_core"><%:Backup Exclude Cores%></option>
|
|
<option value="backup_only_core"><%:Backup Core%></option>
|
|
<option value="backup_only_config"><%:Backup Config%></option>
|
|
<option value="backup_only_rule"><%:Backup Rule Provider%></option>
|
|
<option value="backup_only_proxy"><%:Backup Proxy Provider%></option>
|
|
</select>
|
|
</div>
|
|
<button type="button" class="footer-btn" onclick="return backup_file(this)">
|
|
<svg width="14" height="14"><use href="#oc-icon-download"/></svg>
|
|
<span><%:Backup File%></span>
|
|
</button>
|
|
<button type="button" class="footer-btn" onclick="return restore_config(this)">
|
|
<svg width="14" height="14"><use href="#oc-icon-history"/></svg>
|
|
<span><%:Restore Default%></span>
|
|
</button>
|
|
<button type="button" class="footer-btn" onclick="return remove_all_core(this)">
|
|
<svg width="14" height="14"><use href="#oc-icon-trash"/></svg>
|
|
<span><%:Remove Core%></span>
|
|
</button>
|
|
</div>
|
|
<div class="select-popup-status-bar">
|
|
<div class="select-popup-status" id="addr-list-status"><%:Fetching data...%></div>
|
|
<div class="select-popup-tip"><%:Click%> <b><%:link%></b> <%:to update all, click%> <b><%:version%></b> <%:to update individually%></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
//<*)\\.)+[a-z]{2,}|'+ // domain name
|
|
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
|
|
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
|
|
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
|
|
'(\\#[-a-z\\d_]*)?'+'\/$','i');
|
|
return !!pattern.test(str);
|
|
}
|
|
|
|
function showHint(message, duration) {
|
|
var hint = document.getElementById('version-hint');
|
|
if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
|
|
if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
|
|
hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + message + '</span>';
|
|
hint.classList.add('show');
|
|
hint.classList.remove('note');
|
|
if (duration === 0) return;
|
|
hintTimer = setTimeout(function() {
|
|
showDefaultNote();
|
|
hintTimer = null;
|
|
}, duration || 5000);
|
|
}
|
|
|
|
function showLogLine(html) {
|
|
var hint = document.getElementById('version-hint');
|
|
if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
|
|
if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
|
|
hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + html + '</span>';
|
|
hint.classList.add('show');
|
|
hint.classList.add('note');
|
|
}
|
|
|
|
function showDefaultNote() {
|
|
var hint = document.getElementById('version-hint');
|
|
if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
|
|
if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
|
|
var tips = [
|
|
'<%:Note: if the update fails, you can manually download and upload%>',
|
|
'<%:Note: the client may not support update, because the firmware with squashfs format will not release flash space after updating%>'
|
|
];
|
|
var tip = tips[Math.floor(Math.random() * tips.length)];
|
|
hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + tip + '</span>';
|
|
hint.classList.add('show');
|
|
hint.classList.add('note');
|
|
defaultTipTimer = setTimeout(function() {
|
|
defaultTipTimer = null;
|
|
showDefaultNote();
|
|
}, 5000);
|
|
}
|
|
|
|
function classifyAddr(url) {
|
|
if (!url || url === '') return 'raw';
|
|
if (/raw\.githubusercontent\.com/i.test(url)) return 'raw';
|
|
if (/jsdelivr|fastly|testingcf/i.test(url)) return 'jsdelivr';
|
|
if (/dl\.dler\.io/i.test(url)) return 'dler';
|
|
return 'proxy';
|
|
}
|
|
|
|
function buildDownloadURL(addr, type, branch, version, sha, arch) {
|
|
var isJsDelivr = classifyAddr(addr) === 'jsdelivr';
|
|
var filename, pathPrefix;
|
|
|
|
if (type === 'core' && _isOix) {
|
|
if (!version) return '';
|
|
filename = 'mihomo-' + arch + '-' + version + '.gz';
|
|
var oixURL = 'https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/' + filename;
|
|
var proxyURL = 'https://dl.dler.io/mihomo-oix/' + filename + '?tag=Pre-Alpha';
|
|
var ctype = classifyAddr(addr);
|
|
if (ctype === 'dler') {
|
|
return proxyURL;
|
|
}
|
|
if (addr && addr !== '' && ctype !== 'raw' && ctype !== 'jsdelivr') {
|
|
return addr + oixURL;
|
|
}
|
|
return proxyURL;
|
|
}
|
|
|
|
if (type === 'plugin') {
|
|
var verNoV = version.replace(/^v/, '');
|
|
if (_pkgType === 'apk') {
|
|
filename = 'luci-app-openclash-' + verNoV + '.apk';
|
|
} else {
|
|
filename = 'luci-app-openclash_' + verNoV + '_all.ipk';
|
|
}
|
|
pathPrefix = 'package/' + branch;
|
|
} else {
|
|
filename = 'clash-' + arch + '.tar.gz';
|
|
var coreSubPath = (smart_enable.value === '1') ? 'smart' : 'meta';
|
|
pathPrefix = 'core/' + branch + '/' + coreSubPath;
|
|
}
|
|
|
|
if (sha) {
|
|
var refPath = (type === 'plugin') ? branch : branch + '/' + coreSubPath;
|
|
if (isJsDelivr)
|
|
return addr + 'gh/vernesong/OpenClash@' + sha + '/' + refPath + '/' + filename;
|
|
else if (classifyAddr(addr) === 'raw')
|
|
return 'https://raw.githubusercontent.com/vernesong/OpenClash/' + sha + '/' + refPath + '/' + filename;
|
|
else
|
|
return addr + 'https://raw.githubusercontent.com/vernesong/OpenClash/' + sha + '/' + refPath + '/' + filename;
|
|
} else {
|
|
if (isJsDelivr)
|
|
return addr + 'gh/vernesong/OpenClash@' + pathPrefix + '/' + filename;
|
|
else if (classifyAddr(addr) === 'raw')
|
|
return 'https://raw.githubusercontent.com/vernesong/OpenClash/' + pathPrefix + '/' + filename;
|
|
else
|
|
return addr + 'https://raw.githubusercontent.com/vernesong/OpenClash/' + pathPrefix + '/' + filename;
|
|
}
|
|
}
|
|
|
|
function findSHAbyVersion(selectEl, targetVer) {
|
|
if (!targetVer || !selectEl) return '';
|
|
for (var i = 0; i < selectEl.options.length; i++) {
|
|
if (selectEl.options[i].getAttribute('data-ver') === targetVer) {
|
|
var v = selectEl.options[i].value;
|
|
return (v && v !== '__latest__') ? v : '';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function escAttr(s) {
|
|
return String(s == null ? '' : s).replace(/&/g, '&').replace(/"/g, '"');
|
|
}
|
|
|
|
function makeVerCell(addrUrl, type, version) {
|
|
var branch = fetchedBranch || release_branch.value || 'dev';
|
|
var arch = core_version.value;
|
|
var selectEl = type === 'plugin'
|
|
? document.getElementById('select-plugin-ver')
|
|
: document.getElementById('select-core-ver');
|
|
var verText = version;
|
|
var isLatest = false;
|
|
|
|
if (!verText) {
|
|
if (type === 'plugin') {
|
|
return null;
|
|
}
|
|
|
|
var selectedOption = selectEl.options[selectEl.selectedIndex];
|
|
isLatest = !selectedOption || selectedOption.value === '__latest__';
|
|
|
|
if (!isLatest) {
|
|
verText = selectedOption.getAttribute('data-ver');
|
|
}
|
|
|
|
if (!verText) return null;
|
|
}
|
|
|
|
var useVersion = (verText === 'Latest') ? '' : verText;
|
|
var sha = useVersion ? findSHAbyVersion(selectEl, useVersion) : '';
|
|
var dlUrl = buildDownloadURL(addrUrl, type, branch, useVersion, sha, arch);
|
|
|
|
var verLink = '<a href="javascript:void(0)" class="ver-link" data-type="' + type + '" data-ver="' + escAttr(useVersion) + '" data-dl="' + escAttr(dlUrl) + '" title="<%:Click to Install%>">' + verText + '</a>';
|
|
if (!dlUrl) {
|
|
return verLink;
|
|
}
|
|
return verLink +
|
|
'<a href="' + escAttr(dlUrl) + '" target="_blank" rel="noopener" class="dl-btn" title="<%:Download%>"><svg width="11" height="11"><use href="#oc-icon-download"/></svg></a>';
|
|
}
|
|
|
|
function updateAddrRow(row, addrUrl, info) {
|
|
var smart = smart_enable.value || '0';
|
|
var latencyEl = row.querySelector('.addr-status');
|
|
var verSpans = row.querySelectorAll('.ver-text');
|
|
var pluginEl = verSpans[0];
|
|
var coreEl = verSpans[1];
|
|
|
|
if (info.latency != null && info.latency > 0) {
|
|
var ms = parseInt(info.latency);
|
|
latencyEl.textContent = ms + ' ms';
|
|
latencyEl.className = 'addr-status ' + (ms <= 500 ? 'fast' : ms <= 1000 ? 'medium' : 'slow');
|
|
latencyEl.title = '';
|
|
} else if (info.latency === -1) {
|
|
latencyEl.textContent = '<%:Access Timed Out%>';
|
|
latencyEl.className = 'addr-status err';
|
|
latencyEl.title = '';
|
|
} else if (info.latency === -2) {
|
|
latencyEl.textContent = '<%:Access Denied%>';
|
|
latencyEl.className = 'addr-status err';
|
|
latencyEl.title = '';
|
|
} else {
|
|
latencyEl.textContent = '--';
|
|
latencyEl.className = 'addr-status';
|
|
latencyEl.title = '';
|
|
}
|
|
|
|
if (pluginEl) {
|
|
if (info.plugin_ver) {
|
|
pluginEl.innerHTML = makeVerCell(addrUrl, 'plugin', info.plugin_ver);
|
|
pluginEl.classList.add('has-data');
|
|
pluginEl.classList.remove('err');
|
|
} else {
|
|
var selLink = makeVerCell(addrUrl, 'plugin');
|
|
if (selLink) {
|
|
pluginEl.innerHTML = selLink;
|
|
pluginEl.classList.add('has-data');
|
|
pluginEl.classList.remove('err');
|
|
} else if (info.latency === -1 || info.latency === -2) {
|
|
pluginEl.textContent = info.latency === -1 ? '<%:Access Timed Out%>' : '<%:Access Denied%>';
|
|
pluginEl.classList.add('has-data', 'err');
|
|
} else {
|
|
pluginEl.textContent = '--';
|
|
pluginEl.classList.remove('has-data', 'err');
|
|
}
|
|
}
|
|
}
|
|
if (coreEl) {
|
|
var coreVer = (smart === '1' && info.core_smart_ver) ? info.core_smart_ver : info.core_meta_ver;
|
|
if (coreVer) {
|
|
coreEl.innerHTML = makeVerCell(addrUrl, 'core', coreVer);
|
|
coreEl.classList.add('has-data');
|
|
coreEl.classList.remove('err');
|
|
} else if (info.latency === -1 || info.latency === -2 || info.latency === -3 || info.latency == null) {
|
|
if (info.latency === -1) {
|
|
coreEl.textContent = '<%:Access Timed Out%>';
|
|
coreEl.classList.add('has-data', 'err');
|
|
} else if (info.latency === -2) {
|
|
coreEl.textContent = '<%:Access Denied%>';
|
|
coreEl.classList.add('has-data', 'err');
|
|
} else {
|
|
coreEl.textContent = '--';
|
|
coreEl.classList.remove('has-data', 'err');
|
|
}
|
|
} else {
|
|
var selLink = makeVerCell(addrUrl, 'core');
|
|
if (selLink) {
|
|
coreEl.innerHTML = selLink;
|
|
coreEl.classList.add('has-data');
|
|
coreEl.classList.remove('err');
|
|
} else if (info.core_error) {
|
|
coreEl.textContent = info.core_error === 'timeout' ? '<%:Access Timed Out%>' : '<%:Access Denied%>';
|
|
coreEl.classList.add('has-data', 'err');
|
|
} else {
|
|
coreEl.textContent = '<%:Access Timed Out%>';
|
|
coreEl.classList.add('has-data', 'err');
|
|
}
|
|
}
|
|
}
|
|
|
|
infoCache[addrUrl] = {
|
|
plugin_ver: info.plugin_ver || null,
|
|
core_meta_ver: info.core_meta_ver || null,
|
|
core_smart_ver: info.core_smart_ver || null,
|
|
latency: info.latency,
|
|
core_error: info.core_error || null
|
|
};
|
|
}
|
|
|
|
function buildAddrRows(st) {
|
|
var listBody = document.querySelector('#addr-list-body .addr-list-scroll') || document.getElementById('addr-list-body');
|
|
var customOpt = document.getElementById('custom-addr-option');
|
|
var existing = {};
|
|
listBody.querySelectorAll('.addr-row').forEach(function(row) {
|
|
var v = row.dataset.value;
|
|
if (v) existing[v] = true;
|
|
});
|
|
function addRow(val) {
|
|
if (!val || existing[val]) return;
|
|
var div = document.createElement('div');
|
|
div.className = 'select-option addr-row';
|
|
div.dataset.value = val;
|
|
div.innerHTML = '<span class="addr-row-index"></span><span class="addr-row-url">' + val + '</span><span class="addr-status">--</span><span class="ver-text ver-plugin">--</span><span class="ver-text ver-core">--</span>';
|
|
listBody.insertBefore(div, customOpt);
|
|
existing[val] = true;
|
|
}
|
|
var mod = st.github_address_mod;
|
|
if (mod && mod !== '0') addRow(mod);
|
|
(st.cdn_list || []).forEach(addRow);
|
|
}
|
|
|
|
function renderProxyStates() {
|
|
document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
|
|
if (classifyAddr(row.dataset.value) === 'raw') return;
|
|
var el = row.querySelector('.addr-proxy');
|
|
if (!el) {
|
|
el = document.createElement('span');
|
|
el.className = 'addr-proxy';
|
|
row.appendChild(el);
|
|
}
|
|
var on = (row.dataset.value === currentProxyAddr);
|
|
el.textContent = on ? '<%:Enabled%>' : '<%:Disabled%>';
|
|
el.className = 'addr-proxy ' + (on ? 'on' : 'off');
|
|
});
|
|
}
|
|
|
|
function toggleProxy(row) {
|
|
var val = row.dataset.value;
|
|
if (!val) return;
|
|
var newVal = (val === currentProxyAddr) ? '0' : val;
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_github_address_mod")%>', {value: newVal}, function(x) {
|
|
if (x && x.status == 200) {
|
|
currentProxyAddr = newVal;
|
|
renderProxyStates();
|
|
} else {
|
|
showHint('<%:Failed to save%>');
|
|
}
|
|
});
|
|
}
|
|
|
|
function fetchUpdateInfo(opts) {
|
|
core_version.disabled = true;
|
|
release_branch.disabled = true;
|
|
smart_enable.disabled = true;
|
|
document.getElementById('select-plugin-ver').disabled = true;
|
|
document.getElementById('select-core-ver').disabled = true;
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update")%>', null, function(x, st) {
|
|
if (x && x.status == 200) {
|
|
if (st.corever && st.corever != '0') core_version.value = st.corever; else core_version.value = '0';
|
|
if (st.release_branch) release_branch.value = st.release_branch; else release_branch.value = 'master';
|
|
if (st.smart_enable) smart_enable.value = st.smart_enable; else smart_enable.value = '0';
|
|
_isOix = !!(st.oix_core);
|
|
_pkgType = st.pkg_type || 'opkg';
|
|
document.getElementById('installed-plugin').textContent = (st.opcv && st.opcv != '0') ? st.opcv : '--';
|
|
document.getElementById('installed-core').textContent = (st.coremetacv && st.coremetacv != '0') ? st.coremetacv : '--';
|
|
var cpuArchEl = document.getElementById('CPU_ARCH_VALUE');
|
|
if (cpuArchEl) {
|
|
cpuArchEl.textContent = (st.coremodel && st.coremodel != '') ? st.coremodel : '--';
|
|
}
|
|
currentProxyAddr = (st.github_address_mod || '0');
|
|
buildAddrRows(st);
|
|
renderProxyStates();
|
|
}
|
|
core_version.disabled = false;
|
|
release_branch.disabled = false;
|
|
smart_enable.disabled = false;
|
|
fetchedBranch = release_branch.value;
|
|
updateDataStatus({ update: true });
|
|
fetchVersionHistory(!!(opts && opts.force), {cancel: true});
|
|
refreshAddrInfo({cancel: true, user: true});
|
|
});
|
|
}
|
|
|
|
function initAllData() {
|
|
fetchUpdateInfo();
|
|
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
|
|
}
|
|
|
|
function fetchVersionHistory(force, opts) {
|
|
if (document.getElementById('selectPopup').classList.contains('hidden')) return;
|
|
if (versionHistoryBusy) {
|
|
if (!(opts && opts.cancel)) return;
|
|
if (versionHistoryXHR) { try { versionHistoryXHR.abort(); } catch(e) {} versionHistoryXHR = null; }
|
|
versionHistoryBusy = false;
|
|
}
|
|
versionHistoryBusy = true;
|
|
updateDataStatus({ version: false });
|
|
var branch = fetchedBranch || release_branch.value || 'dev';
|
|
var pluginSelect = document.getElementById('select-plugin-ver');
|
|
var coreSelect = document.getElementById('select-core-ver');
|
|
var hint = document.getElementById('version-hint');
|
|
var params = { branch: branch };
|
|
var smart = smart_enable.value || '0';
|
|
|
|
var appendHistoryEntry = function(entry) {
|
|
if (entry.type === 'plugin' && entry.version) {
|
|
pluginSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
|
|
} else if (!_isOix && entry.type === 'core_meta' && smart !== '1' && entry.version) {
|
|
coreSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
|
|
} else if (!_isOix && entry.type === 'core_smart' && smart === '1' && entry.version) {
|
|
coreSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
|
|
}
|
|
};
|
|
|
|
if (force) params.force = '1';
|
|
|
|
pluginSelect.disabled = true;
|
|
coreSelect.disabled = true;
|
|
|
|
pluginSelect.innerHTML = '<option value="__latest__"><%:Latest%></option>';
|
|
coreSelect.innerHTML = '<option value="__latest__"><%:Latest%></option>';
|
|
|
|
var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "version_history")%>?' +
|
|
Object.keys(params).map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.timeout = 0;
|
|
xhr.open('GET', url, true);
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
var lastLen = 0;
|
|
var pendingBuf = '';
|
|
xhr.onprogress = function() {
|
|
if (xhr.status != 200) return;
|
|
var text = xhr.responseText;
|
|
if (text.length <= lastLen) return;
|
|
var newText = text.substring(lastLen);
|
|
lastLen = text.length;
|
|
pendingBuf += newText;
|
|
var lines = pendingBuf.split('\n');
|
|
pendingBuf = lines.pop();
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) continue;
|
|
try {
|
|
var entry = JSON.parse(line);
|
|
if (entry.complete) {
|
|
if (entry.error) {
|
|
showHint('<%:Failed to get version history%>');
|
|
pluginSelect.disabled = true;
|
|
coreSelect.disabled = true;
|
|
if (!versionRetryTimer) {
|
|
versionRetryTimer = setInterval(fetchVersionHistory, 10000);
|
|
}
|
|
} else {
|
|
pluginSelect.disabled = false;
|
|
coreSelect.disabled = false;
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
}
|
|
continue;
|
|
}
|
|
appendHistoryEntry(entry);
|
|
} catch(e) {}
|
|
}
|
|
};
|
|
xhr.onload = function() {
|
|
if (xhr.status != 200 || !xhr.responseText) {
|
|
showHint('<%:Failed to get version history%>');
|
|
if (!versionRetryTimer) {
|
|
versionRetryTimer = setInterval(fetchVersionHistory, 10000);
|
|
}
|
|
return;
|
|
}
|
|
var text = xhr.responseText;
|
|
if (text.length > lastLen) {
|
|
var newText = text.substring(lastLen);
|
|
lastLen = text.length;
|
|
pendingBuf += newText;
|
|
var lines = pendingBuf.split('\n');
|
|
pendingBuf = lines.pop();
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) continue;
|
|
try {
|
|
var entry = JSON.parse(line);
|
|
if (entry.complete) {
|
|
if (entry.error) {
|
|
showHint('<%:Failed to get version history%>');
|
|
pluginSelect.disabled = true;
|
|
coreSelect.disabled = true;
|
|
if (!versionRetryTimer) {
|
|
versionRetryTimer = setInterval(fetchVersionHistory, 10000);
|
|
}
|
|
} else {
|
|
pluginSelect.disabled = false;
|
|
coreSelect.disabled = false;
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
}
|
|
continue;
|
|
}
|
|
appendHistoryEntry(entry);
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
if (pendingBuf && pendingBuf.trim()) {
|
|
var line = pendingBuf.trim();
|
|
pendingBuf = '';
|
|
try {
|
|
var entry = JSON.parse(line);
|
|
if (entry.complete) {
|
|
if (entry.error) {
|
|
showHint('<%:Failed to get version history%>');
|
|
pluginSelect.disabled = true;
|
|
coreSelect.disabled = true;
|
|
if (!versionRetryTimer) {
|
|
versionRetryTimer = setInterval(fetchVersionHistory, 10000);
|
|
}
|
|
} else {
|
|
pluginSelect.disabled = false;
|
|
coreSelect.disabled = false;
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
}
|
|
} else {
|
|
appendHistoryEntry(entry);
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
};
|
|
xhr.onerror = function() {
|
|
showHint('<%:Failed to get version history%>');
|
|
if (!versionRetryTimer) {
|
|
versionRetryTimer = setInterval(fetchVersionHistory, 10000);
|
|
}
|
|
};
|
|
xhr.onloadend = function() {
|
|
if (versionHistoryXHR === xhr) {
|
|
versionHistoryXHR = null;
|
|
versionHistoryBusy = false;
|
|
}
|
|
updateDataStatus({ version: true });
|
|
};
|
|
xhr.send();
|
|
versionHistoryXHR = xhr;
|
|
}
|
|
|
|
function saveOptions() {
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {
|
|
core_ver: core_version.value,
|
|
release_branch: release_branch.value,
|
|
smart_enable: smart_enable.value
|
|
},function() {});
|
|
}
|
|
|
|
function onArchChange() {
|
|
saveOptions();
|
|
document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
|
|
var addrUrl = row.dataset.value;
|
|
if (!addrUrl || addrUrl === 'custom') return;
|
|
var info = infoCache[addrUrl];
|
|
if (info) updateAddrRow(row, addrUrl, info);
|
|
});
|
|
}
|
|
|
|
function onBranchChange() {
|
|
fetchedBranch = release_branch.value;
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
saveOptions();
|
|
resetDataStatus(true);
|
|
fetchVersionHistory(false, {cancel: true});
|
|
forceRefresh = true;
|
|
refreshAddrInfo({cancel: true, user: true});
|
|
}
|
|
|
|
function onSmartChange() {
|
|
saveOptions();
|
|
resetDataStatus(true);
|
|
fetchVersionHistory(false, {cancel: true});
|
|
forceRefresh = true;
|
|
refreshAddrInfo({cancel: true, user: true});
|
|
}
|
|
|
|
function setAddrListLoading(loading) {
|
|
var overlay = document.getElementById('addr-list-loading');
|
|
if (overlay) overlay.classList.toggle('oc-hidden', !loading);
|
|
}
|
|
|
|
function clearAddrRows() {
|
|
document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
|
|
var latencyEl = row.querySelector('.addr-status');
|
|
var verSpans = row.querySelectorAll('.ver-text');
|
|
if (latencyEl) {
|
|
latencyEl.textContent = '--';
|
|
latencyEl.className = 'addr-status';
|
|
}
|
|
verSpans.forEach(function(el) {
|
|
el.textContent = '--';
|
|
el.classList.remove('has-data', 'err');
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateDataStatus(flags) {
|
|
if (flags) {
|
|
if ('update' in flags) reqUpdateDone = flags.update;
|
|
if ('version' in flags) reqVersionHistoryDone = flags.version;
|
|
if ('addr' in flags) reqAddrInfoDone = flags.addr;
|
|
}
|
|
var status = document.getElementById('addr-list-status');
|
|
if (!status) return;
|
|
if (addrInfoError) {
|
|
status.textContent = '<%:Failed to get data, will retry%>';
|
|
return;
|
|
}
|
|
status.textContent = (reqUpdateDone && reqVersionHistoryDone && reqAddrInfoDone) ? '<%:Ready%>' : '<%:Fetching data...%>';
|
|
}
|
|
|
|
function resetDataStatus(keepUpdate) {
|
|
if (!keepUpdate) reqUpdateDone = false;
|
|
reqVersionHistoryDone = false;
|
|
reqAddrInfoDone = false;
|
|
addrInfoError = false;
|
|
updateDataStatus();
|
|
}
|
|
|
|
function refreshAddrInfo(opts) {
|
|
if (document.getElementById('selectPopup').classList.contains('hidden')) return;
|
|
var addrs = [];
|
|
document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
|
|
var v = row.dataset.value;
|
|
if (v && v !== 'custom' && isValidURL(v)) addrs.push(v);
|
|
});
|
|
if (addrs.length === 0) return;
|
|
if (addrInfoBusy) {
|
|
if (!(opts && opts.cancel)) return;
|
|
if (addrInfoXHR) { try { addrInfoXHR.abort(); } catch(e) {} addrInfoXHR = null; }
|
|
addrInfoBusy = false;
|
|
}
|
|
addrInfoBusy = true;
|
|
if (opts && opts.user) updateDataStatus({ addr: false });
|
|
|
|
var branch = fetchedBranch || release_branch.value || 'dev';
|
|
var smart = smart_enable.value || '0';
|
|
var pluginVer = document.getElementById('select-plugin-ver').value;
|
|
var coreVer = document.getElementById('select-core-ver').value;
|
|
var params = {
|
|
addrs: addrs.join(','),
|
|
branch: branch,
|
|
smart: smart,
|
|
plugin_ver: pluginVer || '',
|
|
core_ver: coreVer || ''
|
|
};
|
|
if (opts && opts.merge) {
|
|
params.merge = '1';
|
|
}
|
|
if (forceRefresh) {
|
|
params.force = '1';
|
|
forceRefresh = false;
|
|
infoCache = {};
|
|
addrDataReceived = false;
|
|
showDefaultNote();
|
|
clearAddrRows();
|
|
}
|
|
if (opts && opts.user && !addrDataReceived) setAddrListLoading(true);
|
|
var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "addr_info")%>?' +
|
|
Object.keys(params).map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.timeout = 0;
|
|
xhr.open('GET', url, true);
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
var lastLen = 0;
|
|
var pendingBuf = '';
|
|
xhr.onprogress = function() {
|
|
if (xhr.status != 200) return;
|
|
var text = xhr.responseText;
|
|
if (text.length <= lastLen) return;
|
|
var newText = text.substring(lastLen);
|
|
lastLen = text.length;
|
|
pendingBuf += newText;
|
|
var lines = pendingBuf.split('\n');
|
|
pendingBuf = lines.pop();
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) continue;
|
|
try {
|
|
var info = JSON.parse(line);
|
|
if (info.complete) {
|
|
if (info.error === 'version_stale') {
|
|
fetchVersionHistory(true);
|
|
}
|
|
continue;
|
|
}
|
|
var addrUrl = info.addr;
|
|
if (!addrUrl) continue;
|
|
if (!addrDataReceived) { addrDataReceived = true; setAddrListLoading(false); }
|
|
var rows = document.querySelectorAll('#addr-list-body .addr-row[data-value="' + CSS.escape(addrUrl) + '"]');
|
|
rows.forEach(function(row) { updateAddrRow(row, addrUrl, info); });
|
|
} catch(e) {}
|
|
}
|
|
};
|
|
xhr.onload = function() {
|
|
if (xhr.status != 200 || !xhr.responseText) {
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
addrInfoError = true;
|
|
if (!retryTimer) {
|
|
retryTimer = setInterval(refreshAddrInfo, 10000);
|
|
}
|
|
return;
|
|
}
|
|
addrInfoError = false;
|
|
var text = xhr.responseText;
|
|
if (text.length > lastLen) {
|
|
var newText = text.substring(lastLen);
|
|
lastLen = text.length;
|
|
pendingBuf += newText;
|
|
var lines = pendingBuf.split('\n');
|
|
pendingBuf = lines.pop();
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) continue;
|
|
try {
|
|
var info = JSON.parse(line);
|
|
if (info.complete) {
|
|
if (info.error === 'version_stale') {
|
|
fetchVersionHistory(true);
|
|
}
|
|
continue;
|
|
}
|
|
var addrUrl = info.addr;
|
|
if (!addrUrl) continue;
|
|
if (!addrDataReceived) { addrDataReceived = true; setAddrListLoading(false); }
|
|
var rows = document.querySelectorAll('#addr-list-body .addr-row[data-value="' + CSS.escape(addrUrl) + '"]');
|
|
rows.forEach(function(row) { updateAddrRow(row, addrUrl, info); });
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
if (!refreshTimer) {
|
|
refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
|
|
}
|
|
};
|
|
xhr.onerror = function() {
|
|
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
addrInfoError = true;
|
|
retryTimer = setInterval(refreshAddrInfo, 10000);
|
|
};
|
|
xhr.onloadend = function() {
|
|
if (addrInfoXHR === xhr) {
|
|
addrInfoXHR = null;
|
|
addrInfoBusy = false;
|
|
}
|
|
if (opts && opts.user) updateDataStatus({ addr: true });
|
|
};
|
|
xhr.send();
|
|
addrInfoXHR = xhr;
|
|
}
|
|
|
|
function initHeaderDrag() {
|
|
var popup = document.getElementById('selectPopup');
|
|
var header = popup ? popup.querySelector('.select-popup-header') : null;
|
|
if (!popup || !header || popup.classList.contains('select-popup-tab')) return;
|
|
if (header.dataset.ocDragInit) return;
|
|
header.dataset.ocDragInit = '1';
|
|
header.classList.add('oc-draggable');
|
|
|
|
var dragging = false;
|
|
var startX = 0, startY = 0, startLeft = 0, startTop = 0;
|
|
|
|
header.addEventListener('pointerdown', function(e) {
|
|
if (e.target.closest && e.target.closest('button, a, select, input')) return;
|
|
dragging = true;
|
|
var rect = popup.getBoundingClientRect();
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
startLeft = rect.left;
|
|
startTop = rect.top;
|
|
popup.style.transform = 'none';
|
|
popup.style.left = rect.left + 'px';
|
|
popup.style.top = rect.top + 'px';
|
|
popup.style.transition = 'none';
|
|
if (header.setPointerCapture) {
|
|
try { header.setPointerCapture(e.pointerId); } catch(err) {}
|
|
}
|
|
e.preventDefault();
|
|
});
|
|
|
|
header.addEventListener('pointermove', function(e) {
|
|
if (!dragging) return;
|
|
popup.style.left = (startLeft + (e.clientX - startX)) + 'px';
|
|
popup.style.top = (startTop + (e.clientY - startY)) + 'px';
|
|
});
|
|
|
|
function stopDrag(e) {
|
|
if (!dragging) return;
|
|
dragging = false;
|
|
popup.style.transition = '';
|
|
if (header.releasePointerCapture) {
|
|
try { header.releasePointerCapture(e.pointerId); } catch(err) {}
|
|
}
|
|
}
|
|
header.addEventListener('pointerup', stopDrag);
|
|
header.addEventListener('pointercancel', stopDrag);
|
|
}
|
|
|
|
function update(btn, type) {
|
|
var selectPopup = document.getElementById('selectPopup');
|
|
var customOptionInput = document.getElementById('customOptionInput');
|
|
var addCustomOption = document.getElementById('addCustomOption');
|
|
|
|
currentDownloadType = type || 'one_key';
|
|
customOptionInput.classList.add('oc-hidden');
|
|
addCustomOption.classList.add('oc-hidden');
|
|
customOptionInput.value = 'https://ghfast.top/';
|
|
fetchedBranch = null;
|
|
addrDataReceived = false;
|
|
resetDataStatus();
|
|
|
|
selectPopup.classList.remove('hidden');
|
|
selectPopup.onclick = function(event) { click_handler(event); };
|
|
setAddrListLoading(true);
|
|
initAllData();
|
|
initHeaderDrag();
|
|
}
|
|
|
|
function handleVersionUpdate(verLink) {
|
|
var row = verLink.closest ? verLink.closest('.addr-row') : null;
|
|
var addrUrl = row ? row.dataset.value : '';
|
|
var type = verLink.getAttribute('data-type');
|
|
var ver = verLink.getAttribute('data-ver') || '';
|
|
var dlUrl = verLink.getAttribute('data-dl') || '';
|
|
|
|
var branch = fetchedBranch || release_branch.value || 'dev';
|
|
var arch = core_version.value;
|
|
var selectEl = type === 'plugin'
|
|
? document.getElementById('select-plugin-ver')
|
|
: document.getElementById('select-core-ver');
|
|
|
|
if (type === 'core' && (!arch || arch === '0')) {
|
|
showHint('<%:No Compiled Version Selected, Please Select In Update Page And Try Again%>');
|
|
return;
|
|
}
|
|
var sha = ver ? findSHAbyVersion(selectEl, ver) : '';
|
|
var selOpt = selectEl.options[selectEl.selectedIndex];
|
|
var isLatest = !selOpt || selOpt.value === '__latest__';
|
|
if (ver && !sha && !isLatest && !(type === 'core' && _isOix)) {
|
|
showHint('<%:The selected version is not in the version history, please refresh and try again%>');
|
|
return;
|
|
}
|
|
|
|
if (ver) {
|
|
dlUrl = buildDownloadURL(addrUrl, type, branch, ver, sha, arch);
|
|
}
|
|
|
|
showDefaultNote();
|
|
|
|
var addrInfo = infoCache[addrUrl];
|
|
if (!addrInfo || addrInfo.latency == null) {
|
|
showHint('<%:Version info not ready, please wait and try again%>');
|
|
return;
|
|
}
|
|
if (addrInfo.latency === -1 || addrInfo.latency === -2 || addrInfo.latency === -3) {
|
|
var errMsg = addrInfo.latency === -1 ? '<%:Link access timed out, cannot download%>' : '<%:Link access failed, cannot download%>';
|
|
showHint(errMsg);
|
|
return;
|
|
}
|
|
|
|
if (!dlUrl) {
|
|
showHint('<%:Failed to get download link%>');
|
|
return;
|
|
}
|
|
|
|
if (type === 'plugin') {
|
|
var installedVer = (document.getElementById('installed-plugin').textContent || '').trim();
|
|
if (ver && installedVer && installedVer !== '--' && installedVer === ver) {
|
|
showHint('<%:The selected version is the same as the installed version, no need to update%>');
|
|
return;
|
|
}
|
|
} else {
|
|
var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
|
|
if (ver && installedCoreVer && installedCoreVer !== '--' && installedCoreVer === ver) {
|
|
showHint('<%:The selected version is the same as the installed version, no need to update%>');
|
|
return;
|
|
}
|
|
}
|
|
|
|
startLog('<%:Checking...%>', false, type === 'core' ? 'openclash_core.sh' : 'openclash_update.sh');
|
|
|
|
var addr = addrUrl;
|
|
if (addr === 'https://raw.githubusercontent.com/') addr = '';
|
|
|
|
if (type === 'core') {
|
|
var params = {download_url: dlUrl};
|
|
if (addr && addr !== '') params.url = addr;
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "core_download")%>', params, function() {});
|
|
} else {
|
|
var pParams = {url: addr, download_url: dlUrl, update_type: 'plugin'};
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "one_key_update")%>', pParams, function() {});
|
|
}
|
|
}
|
|
|
|
function initPopupResizeObserver() {
|
|
var popup = document.getElementById('selectPopup');
|
|
if (!popup || typeof ResizeObserver === 'undefined') return;
|
|
if (popup.dataset.ocResizeInit) return;
|
|
popup.dataset.ocResizeInit = '1';
|
|
new ResizeObserver(function() {
|
|
if (popup.offsetWidth > 0 && popup.offsetHeight > 0) {
|
|
if (popup.classList.contains('select-popup-tab')) return;
|
|
if (popup.style.left || popup.style.top) return;
|
|
popup.style.transform = 'translate(' + Math.round(-popup.offsetWidth / 2) + 'px, ' + Math.round(-popup.offsetHeight / 2) + 'px)';
|
|
}
|
|
}).observe(popup);
|
|
}
|
|
|
|
function click_handler(event) {
|
|
var target = event.target;
|
|
var customInput = document.getElementById('customOptionInput');
|
|
var addBtn = document.getElementById('addCustomOption');
|
|
var optionElem = target.closest ? target.closest('.select-option') : null;
|
|
|
|
var staticEl = target.closest ? target.closest('.addr-proxy-static') : null;
|
|
if (staticEl) return;
|
|
|
|
var verLink = target.closest ? target.closest('.ver-link') : null;
|
|
if (verLink) {
|
|
handleVersionUpdate(verLink);
|
|
return;
|
|
}
|
|
var dlBtn = target.closest ? target.closest('.dl-btn') : null;
|
|
if (dlBtn) {
|
|
return;
|
|
}
|
|
var proxyEl = target.closest ? target.closest('.addr-proxy') : null;
|
|
if (proxyEl) {
|
|
var prow = proxyEl.closest('.addr-row');
|
|
if (prow) toggleProxy(prow);
|
|
return;
|
|
}
|
|
|
|
if (optionElem && optionElem.dataset.value === 'custom' && !customInput.classList.contains('oc-hidden')) {
|
|
customInput.classList.add('oc-hidden');
|
|
addBtn.classList.add('oc-hidden');
|
|
} else if (optionElem && optionElem.dataset.value === 'custom') {
|
|
customInput.classList.remove('oc-hidden');
|
|
addBtn.classList.remove('oc-hidden');
|
|
customInput.focus();
|
|
} else if (target === customInput) {
|
|
customInput.classList.remove('oc-hidden');
|
|
addBtn.classList.remove('oc-hidden');
|
|
} else if (target === addBtn) {
|
|
var val = customInput.value.trim();
|
|
if (val !== '' && isValidURL(val)) {
|
|
var listBody = document.querySelector('#addr-list-body .addr-list-scroll') || document.getElementById('addr-list-body');
|
|
var div = document.createElement('div');
|
|
div.className = 'select-option addr-row';
|
|
div.dataset.value = val;
|
|
div.innerHTML = '<span class="addr-row-index"></span><span class="addr-row-url">' + val + '</span><span class="addr-status">--</span><span class="ver-text ver-plugin">--</span><span class="ver-text ver-core">--</span>';
|
|
listBody.insertBefore(div, document.getElementById('custom-addr-option'));
|
|
customInput.classList.add('oc-hidden');
|
|
addBtn.classList.add('oc-hidden');
|
|
renderProxyStates();
|
|
refreshAddrInfo({merge: true, cancel: true, user: true});
|
|
} else {
|
|
alert("<%:Please enter a valid URL!%>");
|
|
}
|
|
} else if (optionElem && optionElem.classList.contains('addr-row') && target.closest('.addr-row-url')) {
|
|
var addr = optionElem.dataset.value;
|
|
|
|
showDefaultNote();
|
|
|
|
var addrInfo = infoCache[addr];
|
|
if (!addrInfo || addrInfo.latency == null) {
|
|
showHint('<%:Version info not ready, please wait and try again%>');
|
|
return;
|
|
}
|
|
if (addrInfo.latency === -1 || addrInfo.latency === -2 || addrInfo.latency === -3) {
|
|
var errMsg = addrInfo.latency === -1 ? '<%:Link access timed out, cannot download%>' : '<%:Link access failed, cannot download%>';
|
|
showHint(errMsg);
|
|
return;
|
|
}
|
|
|
|
var branch = fetchedBranch || release_branch.value || 'dev';
|
|
var arch = core_version.value;
|
|
var v = core_version.value;
|
|
var r = release_branch.value;
|
|
var s = smart_enable.value;
|
|
|
|
if (!arch || arch === '0') {
|
|
showHint('<%:No Compiled Version Selected, Please Select In Update Page And Try Again%>');
|
|
return;
|
|
}
|
|
|
|
var pluginSel = document.getElementById('select-plugin-ver');
|
|
var coreSel = document.getElementById('select-core-ver');
|
|
var pluginOpt = pluginSel.options[pluginSel.selectedIndex];
|
|
var coreOpt = coreSel.options[coreSel.selectedIndex];
|
|
var isPluginLatest = !pluginOpt || pluginOpt.value === '__latest__';
|
|
var isCoreLatest = !coreOpt || coreOpt.value === '__latest__';
|
|
|
|
var pluginVer = addrInfo.plugin_ver || '';
|
|
var coreVer = (s === '1' && addrInfo.core_smart_ver)
|
|
? addrInfo.core_smart_ver
|
|
: (addrInfo.core_meta_ver || '');
|
|
|
|
var pluginSHA = (!isPluginLatest && addrInfo.plugin_ver) ? findSHAbyVersion(pluginSel, addrInfo.plugin_ver) : '';
|
|
var useCoreVer = (s === '1' && addrInfo.core_smart_ver) ? addrInfo.core_smart_ver : addrInfo.core_meta_ver;
|
|
var coreSHA = (!isCoreLatest && useCoreVer) ? findSHAbyVersion(coreSel, useCoreVer) : '';
|
|
|
|
if (currentDownloadType !== 'core_download' && !isPluginLatest && addrInfo.plugin_ver && !pluginSHA) {
|
|
showHint('<%:The selected version is not in the version history, please refresh and try again%>');
|
|
return;
|
|
}
|
|
if (!isCoreLatest && useCoreVer && !coreSHA) {
|
|
showHint('<%:The selected version is not in the version history, please refresh and try again%>');
|
|
return;
|
|
}
|
|
|
|
if (currentDownloadType !== 'core_download') {
|
|
// One-key update: both plugin and core will be updated
|
|
if (!pluginVer) {
|
|
showHint('<%:Failed to get plugin version info from this link, please check again later%>');
|
|
return;
|
|
}
|
|
if (!coreVer) {
|
|
showHint('<%:Failed to get core version info from this link, please check again later%>');
|
|
return;
|
|
}
|
|
|
|
var installedVer = (document.getElementById('installed-plugin').textContent || '').trim();
|
|
var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
|
|
var pluginSame = installedVer && installedVer !== '--' && installedVer === pluginVer;
|
|
var coreSame = installedCoreVer && installedCoreVer !== '--' && installedCoreVer === coreVer;
|
|
|
|
if (pluginSame && coreSame) {
|
|
showHint('<%:The selected version is the same as the installed version, no need to update%>');
|
|
return;
|
|
}
|
|
} else {
|
|
// Core download only: check core only
|
|
if (!coreVer) {
|
|
showHint('<%:Failed to get core version info from this link, please check again later%>');
|
|
return;
|
|
}
|
|
|
|
var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
|
|
if (installedCoreVer && installedCoreVer !== '--' && installedCoreVer === coreVer) {
|
|
showHint('<%:The selected version is the same as the installed version, no need to update%>');
|
|
return;
|
|
}
|
|
}
|
|
|
|
startLog('<%:Checking...%>', false, currentDownloadType === 'core_download' ? 'openclash_core.sh' : 'openclash_update.sh');
|
|
|
|
if (addr === 'https://raw.githubusercontent.com/') addr = '';
|
|
|
|
if (currentDownloadType === 'core_download') {
|
|
var coreURL = buildDownloadURL(addr, 'core', r, coreVer, coreSHA, arch);
|
|
var params = {download_url: coreURL};
|
|
if (addr && addr !== '') params.url = addr;
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "core_download")%>', params, function() {});
|
|
} else {
|
|
var pluginURL = buildDownloadURL(addr, 'plugin', r, pluginVer, pluginSHA, arch);
|
|
var oneKeyParams = {url: addr};
|
|
if (!pluginSame) {
|
|
oneKeyParams.download_url = pluginURL;
|
|
}
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "one_key_update")%>', oneKeyParams, function() {});
|
|
}
|
|
}
|
|
}
|
|
|
|
function refreshAllData() {
|
|
showDefaultNote();
|
|
infoCache = {};
|
|
addrDataReceived = false;
|
|
resetDataStatus();
|
|
setAddrListLoading(true);
|
|
forceRefresh = true;
|
|
clearAddrRows();
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
fetchedBranch = null;
|
|
fetchUpdateInfo({force: true});
|
|
refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
|
|
}
|
|
|
|
function closeSelectPopup() {
|
|
var popup = document.getElementById('selectPopup');
|
|
var customInput = document.getElementById('customOptionInput');
|
|
var addBtn = document.getElementById('addCustomOption');
|
|
if (!versionTabMode) {
|
|
popup.classList.add('hidden');
|
|
popup.style.transform = '';
|
|
popup.style.left = '';
|
|
popup.style.top = '';
|
|
popup.style.transition = '';
|
|
}
|
|
if (addrInfoXHR) { try { addrInfoXHR.abort(); } catch(e) {} addrInfoXHR = null; }
|
|
if (versionHistoryXHR) { try { versionHistoryXHR.abort(); } catch(e) {} versionHistoryXHR = null; }
|
|
addrInfoBusy = false;
|
|
versionHistoryBusy = false;
|
|
if (!logStreamActive) { showDefaultNote(); }
|
|
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
|
|
if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
|
|
customInput.classList.add('oc-hidden');
|
|
addBtn.classList.add('oc-hidden');
|
|
}
|
|
|
|
function setBtnBusy(btn, busy, busyText) {
|
|
if (!btn) return;
|
|
var btnText = btn.querySelector('span:not(.loading-spinner)');
|
|
if (busy) {
|
|
if (btnText && !btn.dataset.ocBtnOriginalText) {
|
|
btn.dataset.ocBtnOriginalText = btnText.textContent;
|
|
}
|
|
if (btnText && busyText) { btnText.textContent = busyText; }
|
|
ocSetBtnLoading(btn, true);
|
|
} else {
|
|
ocSetBtnLoading(btn, false);
|
|
if (btnText && btn.dataset.ocBtnOriginalText) {
|
|
btnText.textContent = btn.dataset.ocBtnOriginalText;
|
|
delete btn.dataset.ocBtnOriginalText;
|
|
}
|
|
}
|
|
}
|
|
|
|
function remove_all_core(btn) {
|
|
if (confirm('<%:Are you sure want to remove all core files?%>')) {
|
|
setBtnBusy(btn, true, '<%:Removing...%>');
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "remove_all_core")%>', null, function(x) {
|
|
alert((x && x.status == 200) ? '<%:Remove succeeded!%>' : '<%:Remove failed!%>');
|
|
setBtnBusy(btn, false);
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function backup_file(btn) {
|
|
var endpoint = document.getElementById('BACKUP_SELECT').value;
|
|
var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash")%>' + '/' + endpoint;
|
|
setBtnBusy(btn, true, '<%:Backing up...%>');
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', url, true);
|
|
xhr.responseType = 'blob';
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
xhr.onload = function() {
|
|
setBtnBusy(btn, false);
|
|
if (xhr.status == 200 && xhr.response) {
|
|
var disposition = xhr.getResponseHeader('Content-Disposition') || '';
|
|
var match = disposition.match(/filename="?([^";]+)"?/);
|
|
var filename = match ? match[1] : endpoint + '.tar.gz';
|
|
var blobUrl = URL.createObjectURL(xhr.response);
|
|
var a = document.createElement('a');
|
|
a.href = blobUrl;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
setTimeout(function() { URL.revokeObjectURL(blobUrl); }, 1000);
|
|
} else {
|
|
alert('<%:Backup failed!%>');
|
|
}
|
|
};
|
|
xhr.onerror = function() {
|
|
setBtnBusy(btn, false);
|
|
alert('<%:Backup failed!%>');
|
|
};
|
|
xhr.send();
|
|
return false;
|
|
}
|
|
|
|
function restore_config(btn) {
|
|
if (confirm('<%:Are you sure want to restore the default config?%>')) {
|
|
setBtnBusy(btn, true, '<%:Restoring...%>');
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "restore")%>', null, function(x) {
|
|
setBtnBusy(btn, false);
|
|
if (x && x.status == 200) {
|
|
alert('<%:Restore succeeded!%>');
|
|
} else {
|
|
alert('<%:Restore failed!%>');
|
|
}
|
|
window.location.href = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "settings")%>';
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function startLog(initialMessage, skipClearLog, scriptName) {
|
|
if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
|
|
if (logStreamReconnect) { clearTimeout(logStreamReconnect); logStreamReconnect = null; }
|
|
if (initialMessage) {
|
|
logRetryCount = 0;
|
|
lastLogLineCount = null;
|
|
}
|
|
logStreamActive = true;
|
|
|
|
var watchMode = !!scriptName && scriptName !== 'view';
|
|
var streamUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "startlog")%>';
|
|
if (watchMode) {
|
|
streamUrl += '?script=' + encodeURIComponent(scriptName);
|
|
}
|
|
|
|
if (initialMessage) {
|
|
if (logMaxWaitTimer) { clearTimeout(logMaxWaitTimer); }
|
|
logMaxWaitTimer = setTimeout(function() {
|
|
if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
|
|
logStreamActive = false;
|
|
var hint = document.getElementById('version-hint');
|
|
if (hint) { hint.classList.remove('logging'); }
|
|
}, 600000);
|
|
}
|
|
|
|
if (initialMessage) {
|
|
showLogLine('<b style="color:var(--info-color)">' + initialMessage + '</b>');
|
|
}
|
|
|
|
function openStream() {
|
|
if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.timeout = 0;
|
|
xhr.open('GET', streamUrl, true);
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
var streamEnded = false;
|
|
var gotData = false;
|
|
var processedLines = 0;
|
|
var finishTimer = null;
|
|
|
|
function finalCleanup(showDefault) {
|
|
if (logStreamXHR === xhr) { logStreamXHR = null; }
|
|
try { xhr.abort(); } catch(e) {}
|
|
logStreamActive = false;
|
|
if (showDefault) {
|
|
var hint = document.getElementById('version-hint');
|
|
if (hint) { hint.classList.remove('logging'); }
|
|
showDefaultNote();
|
|
}
|
|
}
|
|
|
|
xhr.onprogress = function() {
|
|
if (logStreamXHR !== xhr) return;
|
|
if (streamEnded) return;
|
|
var text = xhr.responseText || '';
|
|
var startLine = (lastLogLineCount != null) ? lastLogLineCount : 0;
|
|
if (processedLines > startLine) startLine = processedLines;
|
|
var lines = text.split('\n');
|
|
var hasPartial = text.length > 0 && text.charAt(text.length - 1) !== '\n';
|
|
var endIdx = hasPartial ? lines.length - 1 : lines.length;
|
|
var newChunk = lines.slice(startLine, endIdx).join('\n');
|
|
processedLines = endIdx;
|
|
var raw = newChunk.replace(/##FINISHED##|##CONTINUE##|##FINISH##/g, '');
|
|
if (raw.trim()) {
|
|
gotData = true;
|
|
displayLog(raw);
|
|
}
|
|
if (/##FINISHED##\s*$/.test(text) && !finishTimer) {
|
|
finishTimer = setTimeout(function() {
|
|
streamEnded = true;
|
|
finalCleanup(true);
|
|
}, 10000);
|
|
}
|
|
};
|
|
|
|
xhr.onload = function() {
|
|
if (logStreamXHR !== xhr) return;
|
|
if (streamEnded) return;
|
|
var text = xhr.responseText || '';
|
|
if (!watchMode || /##FINISHED##\s*$/.test(text)) {
|
|
if (!finishTimer) {
|
|
finishTimer = setTimeout(function() {
|
|
streamEnded = true;
|
|
finalCleanup(true);
|
|
}, 10000);
|
|
}
|
|
return;
|
|
}
|
|
if (finishTimer) { clearTimeout(finishTimer); finishTimer = null; }
|
|
streamEnded = true;
|
|
lastLogLineCount = text.replace(/##FINISHED##\s*$|##CONTINUE##\s*$/, '').replace(/\n+$/, '').split('\n').length;
|
|
finalCleanup(false);
|
|
if (logStreamReconnect) clearTimeout(logStreamReconnect);
|
|
logStreamReconnect = setTimeout(function() {
|
|
if (!logStreamXHR) startLog(null, true, scriptName);
|
|
}, 1000);
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
if (logStreamXHR !== xhr) return;
|
|
if (finishTimer) { clearTimeout(finishTimer); finishTimer = null; }
|
|
streamEnded = true;
|
|
lastLogLineCount = (xhr.responseText || '').replace(/##FINISHED##\s*$|##CONTINUE##\s*$/, '').replace(/\n+$/, '').split('\n').length;
|
|
finalCleanup(false);
|
|
if (watchMode) {
|
|
if (logStreamReconnect) clearTimeout(logStreamReconnect);
|
|
logStreamReconnect = setTimeout(function() {
|
|
if (!logStreamXHR) startLog(null, true, scriptName);
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
xhr.send();
|
|
logStreamXHR = xhr;
|
|
}
|
|
|
|
if (skipClearLog) {
|
|
openStream();
|
|
} else {
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_start_log")%>', null, function() {
|
|
openStream();
|
|
});
|
|
}
|
|
}
|
|
|
|
function displayLog(logContent) {
|
|
var hint = document.getElementById('version-hint');
|
|
if (!hint) return;
|
|
|
|
var lines = logContent.split('\n');
|
|
var allLines = [];
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var t = lines[i].replace(/##FINISHED##|##CONTINUE##|##FINISH##/g, '').trim().replace(/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s*/, '');
|
|
if (t) allLines.push(t);
|
|
}
|
|
if (allLines.length === 0) return;
|
|
|
|
var maxLines = 3;
|
|
|
|
if (!hint.classList.contains('logging')) {
|
|
hint.classList.add('logging');
|
|
hint.innerHTML = '';
|
|
hint.scrollTop = 0;
|
|
}
|
|
|
|
var existing = [];
|
|
for (var i = 0; i < hint.children.length; i++) {
|
|
var child = hint.children[i];
|
|
if (child.tagName === 'DIV' && child.textContent !== '\u200B' && child.firstChild && child.firstChild.textContent) {
|
|
existing.push(child.firstChild.textContent);
|
|
}
|
|
}
|
|
|
|
var merged = existing.concat(allLines);
|
|
if (merged.length > maxLines) {
|
|
merged = merged.slice(merged.length - maxLines);
|
|
}
|
|
|
|
hint.innerHTML = '';
|
|
hint.scrollTop = 0;
|
|
for (var j = 0; j < merged.length; j++) {
|
|
var color = ocGetLogColor(merged[j]);
|
|
var div = document.createElement('div');
|
|
div.style.whiteSpace = 'nowrap';
|
|
div.innerHTML = '<b style="color:' + color + '">' + merged[j] + '</b>';
|
|
hint.appendChild(div);
|
|
}
|
|
|
|
while (hint.children.length < maxLines) {
|
|
var spacer = document.createElement('div');
|
|
spacer.style.visibility = 'hidden';
|
|
spacer.style.whiteSpace = 'nowrap';
|
|
spacer.textContent = '\u200B';
|
|
hint.insertBefore(spacer, hint.firstChild);
|
|
}
|
|
|
|
hint.style.willChange = 'scroll-position';
|
|
ocAnimateScroll(hint);
|
|
}
|
|
|
|
showDefaultNote();
|
|
if (versionTabMode) {
|
|
var tabPopup = document.getElementById('selectPopup');
|
|
if (tabPopup) {
|
|
tabPopup.onclick = function(event) { click_handler(event); };
|
|
}
|
|
setAddrListLoading(true);
|
|
resetDataStatus();
|
|
initAllData();
|
|
}
|
|
|
|
function stopLogStream() {
|
|
if (logStreamReconnect) { clearTimeout(logStreamReconnect); logStreamReconnect = null; }
|
|
if (logMaxWaitTimer) { clearTimeout(logMaxWaitTimer); logMaxWaitTimer = null; }
|
|
if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
|
|
logStreamActive = false;
|
|
var hint = document.getElementById('version-hint');
|
|
if (hint) { hint.classList.remove('logging'); }
|
|
}
|
|
window.addEventListener('beforeunload', stopLogStream);
|
|
window.addEventListener('pagehide', stopLogStream);
|
|
initPopupResizeObserver();
|
|
//]]>
|
|
</script> |