'use strict'; 'require baseclass'; 'require rpc'; 'require ui'; (function () { if (document.getElementById('clashoo-toast-style')) return; var css = [ '#clashoo-toast-stack{position:fixed;top:20px;right:20px;z-index:99999;display:flex;flex-direction:column;gap:8px;max-width:380px;pointer-events:none;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}', '.clashoo-toast{background:rgba(33,37,41,.92);color:#fff;font-size:13px;line-height:1.5;padding:10px 14px;border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.18);opacity:0;transform:translateX(24px);transition:opacity .25s ease,transform .25s ease;pointer-events:auto;cursor:pointer;word-break:break-word}', '.clashoo-toast.show{opacity:1;transform:translateX(0)}', '.clashoo-toast.err{background:rgba(198,40,40,.94)}', '.clashoo-toast.ok{background:rgba(46,125,50,.94)}', '.clashoo-toast a{color:#8ec5ff;text-decoration:underline}', '.clashoo-toast a:hover{color:#b3d7ff}' ].join(''); var s = document.createElement('style'); s.id = 'clashoo-toast-style'; s.textContent = css; document.head.appendChild(s); })(); function _clashooToastStack() { var el = document.getElementById('clashoo-toast-stack'); if (!el) { el = document.createElement('div'); el.id = 'clashoo-toast-stack'; document.body.appendChild(el); } return el; } function _clashooLinkify(safeText) { var logUrl = L.url('admin/services/clashoo/system'); return safeText.replace(/(进度|日志)/g, '$1'); } function _clashooToast(msg, opts) { opts = opts || {}; var stack = _clashooToastStack(); var el = document.createElement('div'); el.className = 'clashoo-toast' + (opts.kind ? ' ' + opts.kind : ''); var text; if (msg && msg.nodeType === 1) text = msg.textContent || ''; else if (typeof msg === 'string') text = msg; else text = String(msg || ''); var safe = text.replace(/&/g, '&').replace(//g, '>'); el.innerHTML = _clashooLinkify(safe); stack.appendChild(el); requestAnimationFrame(function () { el.classList.add('show'); }); var dur = opts.duration || 3500; var timer = null, remain = dur, start = 0; function close() { if (timer) { clearTimeout(timer); timer = null; } el.classList.remove('show'); setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 280); } function arm() { start = Date.now(); timer = setTimeout(close, remain); } el.addEventListener('mouseenter', function () { if (timer) { clearTimeout(timer); timer = null; remain = Math.max(800, remain - (Date.now() - start)); } }); el.addEventListener('mouseleave', arm); el.addEventListener('click', function (ev) { if (ev.target && ev.target.tagName === 'A') return; close(); }); arm(); return { close: close }; } (function () { if (!ui || ui._clashooPatched) return; ui._clashooPatched = true; var orig = ui.addNotification.bind(ui); ui.addNotification = function (title, children, classes) { try { var text = ''; var collect = function (c) { if (c == null) return; if (Array.isArray(c)) { c.forEach(collect); return; } if (c.nodeType === 1) { text += (c.textContent || '') + ' '; return; } text += String(c) + ' '; }; if (title) text += title + ' '; collect(children); text = text.trim(); var kind = ''; var cls = classes ? String(classes) : ''; if (/error|danger/i.test(cls) || /失败|错误/.test(text)) kind = 'err'; else if (/success/i.test(cls) || /成功/.test(text)) kind = 'ok'; _clashooToast(text, { kind: kind }); return null; } catch (e) { return orig(title, children, classes); } }; })(); const callStatus = rpc.declare({ object: 'luci.clashoo', method: 'status', expect: {} }); const callStart = rpc.declare({ object: 'luci.clashoo', method: 'start', expect: {} }); const callStop = rpc.declare({ object: 'luci.clashoo', method: 'stop', expect: {} }); const callReload = rpc.declare({ object: 'luci.clashoo', method: 'reload', expect: {} }); const callRestart = rpc.declare({ object: 'luci.clashoo', method: 'restart', expect: {} }); const callVersion = rpc.declare({ object: 'luci.clashoo', method: 'version', expect: {} }); const callListProf = rpc.declare({ object: 'luci.clashoo', method: 'list_profiles', expect: {} }); const callListConfigs = rpc.declare({ object: 'luci.clashoo', method: 'list_configs', expect: {} }); const callSetConfig = rpc.declare({ object: 'luci.clashoo', method: 'set_config', params: ['name'], expect: {} }); const callSetMode = rpc.declare({ object: 'luci.clashoo', method: 'set_mode', params: ['mode'], expect: {} }); const callSetProxyMode = rpc.declare({ object: 'luci.clashoo', method: 'set_proxy_mode', params: ['mode'], expect: {} }); const callSetCore = rpc.declare({ object: 'luci.clashoo', method: 'set_core', params: ['core', 'dcore', 'action'], expect: {} }); const callSetPanel = rpc.declare({ object: 'luci.clashoo', method: 'set_panel', params: ['name'], expect: {} }); const callUpdatePanel = rpc.declare({ object: 'luci.clashoo', method: 'update_panel', params: ['name'], expect: {} }); const callPanelStatus = rpc.declare({ object: 'luci.clashoo', method: 'panel_status', expect: {} }); const callReadLog = rpc.declare({ object: 'luci.clashoo', method: 'read_log', expect: {} }); const callReadRealLog = rpc.declare({ object: 'luci.clashoo', method: 'read_real_log', expect: {} }); const callClearLog = rpc.declare({ object: 'luci.clashoo', method: 'clear_log', expect: {} }); const callReadUpdateLog = rpc.declare({ object: 'luci.clashoo', method: 'read_update_log', expect: {} }); const callClearUpdateLog= rpc.declare({ object: 'luci.clashoo', method: 'clear_update_log', expect: {} }); const callReadGeoipLog = rpc.declare({ object: 'luci.clashoo', method: 'read_geoip_log', expect: {} }); const callClearGeoipLog = rpc.declare({ object: 'luci.clashoo', method: 'clear_geoip_log', expect: {} }); const callReadCoreLog = rpc.declare({ object: 'luci.clashoo', method: 'read_core_log', expect: {} }); const callClearCoreLog = rpc.declare({ object: 'luci.clashoo', method: 'clear_core_log', expect: {} }); const callReadUpdateMergedLog = rpc.declare({ object: 'luci.clashoo', method: 'read_update_merged_log', expect: {} }); const callClearUpdateMergedLog = rpc.declare({ object: 'luci.clashoo', method: 'clear_update_merged_log', expect: {} }); const callGetCpuArch = rpc.declare({ object: 'luci.clashoo', method: 'get_cpu_arch', expect: {} }); const callDownloadCore = rpc.declare({ object: 'luci.clashoo', method: 'download_core', expect: {} }); const callUpdateGeoip = rpc.declare({ object: 'luci.clashoo', method: 'update_geoip', expect: {} }); const callGetGeoipVersion = rpc.declare({ object: 'luci.clashoo', method: 'get_geoip_version', expect: {} }); const callComponentStatus = rpc.declare({ object: 'luci.clashoo', method: 'component_status', expect: {} }); const callComponentUpdate = rpc.declare({ object: 'luci.clashoo', method: 'component_update', params: ['component', 'variant'], expect: {} }); const callComponentUpdateLog = rpc.declare({ object: 'luci.clashoo', method: 'component_update_log', expect: {} }); const callComponentCheckUpdates = rpc.declare({ object: 'luci.clashoo', method: 'component_check_updates', expect: {} }); const callUpdateChinaIp = rpc.declare({ object: 'luci.clashoo', method: 'update_china_ip', expect: {} }); const callGetLogStatus = rpc.declare({ object: 'luci.clashoo', method: 'get_log_status', expect: {} }); const callAccessCheck = rpc.declare({ object: 'luci.clashoo', method: 'access_check', expect: {} }); const callAccessCheckRefresh= rpc.declare({ object: 'luci.clashoo', method: 'access_check_refresh',expect: {} }); const callOverviewStats = rpc.declare({ object: 'luci.clashoo', method: 'overview_stats', expect: {} }); const callOverview = rpc.declare({ object: 'luci.clashoo', method: 'overview', expect: {} }); const callSmartFlushCache = rpc.declare({ object: 'luci.clashoo', method: 'smart_flush_cache', expect: {} }); const callListSingboxProfiles = rpc.declare({ object: 'luci.clashoo', method: 'list_singbox_profiles', expect: {} }); const callGetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'get_singbox_profile', params: ['name'], expect: {} }); const callSaveSingboxProfileChunk = rpc.declare({ object: 'luci.clashoo', method: 'save_singbox_profile_chunk', params: ['name', 'content', 'index', 'total'], expect: {} }); const callSetSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'set_singbox_profile', params: ['name'], expect: {} }); const callDeleteSingboxProfile = rpc.declare({ object: 'luci.clashoo', method: 'delete_singbox_profile', params: ['name'], expect: {} }); const callCreateSingboxConfig = rpc.declare({ object: 'luci.clashoo', method: 'create_singbox_config', params: ['sub_url', 'name'], expect: {} }); const callCommitConfig = rpc.declare({ object: 'luci.clashoo', method: 'commit_config', expect: {} }); const callDnsAutoSetup = rpc.declare({ object: 'luci.clashoo', method: 'dns_auto_setup', expect: {} }); const callFetchSingboxNative = rpc.declare({ object: 'luci.clashoo', method: 'fetch_singbox_native', params: ['url', 'name'], expect: {} }); const callUpdateSingboxNative = rpc.declare({ object: 'luci.clashoo', method: 'update_singbox_native', params: ['name'], expect: {} }); // 把脚本输出的英文 log 行翻成中文(去掉前导时间戳) function localizeLogLine(line) { var msg = String(line || '').replace(/^\s*\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s*-?\s*/, ''); var rules = [ // LightGBM 模型更新 [/^Start downloading LightGBM model from:?\s*(.*)$/i, _("Start downloading LightGBM model: $1")], [/^Model unchanged, no update needed$/i, _("Model is already up to date")], [/^LightGBM model updated successfully$/i, _("LightGBM model updated successfully")], [/^Download failed \(rc=(\d+)\)$/i, _("Download failed (error code $1)")], [/^Failed to install model to (.*)$/i, _("Model install failed: $1")], [/^No curl or wget found$/i, _("curl or wget not found")], // GeoIP / GeoSite 更新 [/^Updating (.+)$/i, _("Updating $1")], [/^(.+) updated$/i, _("$1 updated")], [/^GeoIP update completed, apply on next Clashoo restart$/i, _("GeoIP update completed, applies after Clashoo restarts")], [/^GeoIP update completed$/i, _("GeoIP update completed")], [/^GeoIP update failed$/i, _("GeoIP update failed")], // 通用 [/^Download succeeded$/i, _("Download succeeded")], [/^Already up to date$/i, _("Already up to date")] ]; for (var i = 0; i < rules.length; i++) { if (rules[i][0].test(msg)) return msg.replace(rules[i][0], rules[i][1]); } return msg; } return baseclass.extend({ localizeLogLine: localizeLogLine, status: function () { return L.resolveDefault(callStatus(), {}); }, start: function () { return L.resolveDefault(callStart(), {}); }, stop: function () { return L.resolveDefault(callStop(), {}); }, reload: function () { return L.resolveDefault(callReload(), {}); }, restart: function () { return L.resolveDefault(callRestart(), {}); }, version: function () { return L.resolveDefault(callVersion(), {}); }, listProfiles: function () { return L.resolveDefault(callListProf(), { profiles: [] }).then(r => r.profiles || []); }, listConfigs: function () { return L.resolveDefault(callListConfigs(), { configs: [], current: '' }); }, setConfig: function (name) { return L.resolveDefault(callSetConfig(name), {}); }, setMode: function (mode) { return L.resolveDefault(callSetMode(mode), {}); }, setProxyMode: function (mode) { return L.resolveDefault(callSetProxyMode(mode), {}); }, setCore: function (core, dcore, action) { return L.resolveDefault(callSetCore(core, dcore, action), {}); }, setPanel: function (name) { return L.resolveDefault(callSetPanel(name), {}); }, updatePanel: function (name) { return L.resolveDefault(callUpdatePanel(name || 'zashboard'), {}); }, panelStatus: function () { return L.resolveDefault(callPanelStatus(), {}); }, readLog: function () { return L.resolveDefault(callReadLog(), { content: '' }).then(r => r.content || ''); }, readRealLog: function () { return L.resolveDefault(callReadRealLog(), { content: '' }).then(r => r.content || ''); }, clearLog: function () { return L.resolveDefault(callClearLog(), {}); }, readUpdateLog: function () { return L.resolveDefault(callReadUpdateLog(), { content: '' }).then(r => r.content || ''); }, clearUpdateLog: function () { return L.resolveDefault(callClearUpdateLog(), {}); }, readGeoipLog: function () { return L.resolveDefault(callReadGeoipLog(), { content: '' }).then(r => r.content || ''); }, clearGeoipLog: function () { return L.resolveDefault(callClearGeoipLog(), {}); }, readCoreLog: function () { return L.resolveDefault(callReadCoreLog(), { content: '' }).then(r => r.content || ''); }, clearCoreLog: function () { return L.resolveDefault(callClearCoreLog(), {}); }, readUpdateMergedLog: function () { return L.resolveDefault(callReadUpdateMergedLog(), { content: '' }).then(r => r.content || ''); }, clearUpdateMergedLog: function () { return L.resolveDefault(callClearUpdateMergedLog(), {}); }, getCpuArch: function () { return L.resolveDefault(callGetCpuArch(), { arch: '' }).then(r => r.arch || ''); }, downloadCore: function (dcore, arch) { return L.resolveDefault(callDownloadCore({ dcore: dcore, arch: arch }), {}); }, updateGeoip: function () { return L.resolveDefault(callUpdateGeoip(), {}); }, getGeoipVersion: function () { return L.resolveDefault(callGetGeoipVersion(), { version: '' }); }, componentStatus: function () { return L.resolveDefault(callComponentStatus(), { running: false, components: [] }); }, componentUpdate: function (component, variant) { return L.resolveDefault(callComponentUpdate(component, variant || ''), { success: false }); }, componentUpdateLog: function () { return L.resolveDefault(callComponentUpdateLog(), { running: false, content: '' }); }, componentCheckUpdates: function () { return L.resolveDefault(callComponentCheckUpdates(), { ok: false, latest: {} }); }, updateChinaIp: function () { return L.resolveDefault(callUpdateChinaIp(), {}); }, getLogStatus: function () { return L.resolveDefault(callGetLogStatus(), {}); }, accessCheck: function () { return L.resolveDefault(callAccessCheck(), {}); }, accessCheckRefresh: function () { return L.resolveDefault(callAccessCheckRefresh(), { success: false }); }, overviewStats: function () { return L.resolveDefault(callOverviewStats(), {}); }, overview: function () { return L.resolveDefault(callOverview(), { status: {}, stats: {}, configs: { configs: [], current: '', core_type: '' }, access: {} }); }, smartFlushCache: function () { return L.resolveDefault(callSmartFlushCache(), { success: false }); }, listSingboxProfiles: function () { return L.resolveDefault(callListSingboxProfiles(), { profiles: [], active: '' }); }, getSingboxProfile: function (name) { return L.resolveDefault(callGetSingboxProfile(name), {}); }, saveSingboxProfile: function (name, content) { var chunkSize = 24576; var total = Math.max(1, Math.ceil((content || '').length / chunkSize)); var index = 0; function sendNext() { var chunk = (content || '').slice(index * chunkSize, (index + 1) * chunkSize); return L.resolveDefault(callSaveSingboxProfileChunk(name, chunk, String(index), String(total)), {}).then(function (r) { if (!r || !r.success) return { success: false, error: (r && r.error) || 'upload_failed', message: (r && (r.message || r.error)) || _('Upload failed') }; index++; return index < total ? sendNext() : r; }); } return sendNext(); }, setSingboxProfile: function (name) { return L.resolveDefault(callSetSingboxProfile(name), {}); }, deleteSingboxProfile: function (name) { return L.resolveDefault(callDeleteSingboxProfile(name), {}); }, createSingboxConfig: function (url, name) { return L.resolveDefault(callCreateSingboxConfig(url, name), {}); }, commitConfig: function () { return L.resolveDefault(callCommitConfig(), { success: false }); }, dnsAutoSetup: function () { return L.resolveDefault(callDnsAutoSetup(), { success: false }); }, fetchSingboxNative: function (url, name) { return L.resolveDefault(callFetchSingboxNative(url, name), {}); }, updateSingboxNative: function (name) { return L.resolveDefault(callUpdateSingboxNative(name), {}); }, toast: _clashooToast });