update 2026-10-07 18:47:42

This commit is contained in:
action committed 2026-10-07 18:47:42 +08:00
1 parent 617f7ca2a6
commit bd061545ed
4832 files changed
+1693456

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-lucky
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-lucky
PKG_VERSION:=3.0.3
PKG_RELEASE:=15
LUCI_TITLE:=LuCI Support for Dynamic lucky Client
LUCI_DEPENDS:=+lucky
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,192 @@
// Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-lucky
'use strict';
'require form';
'require poll';
'require rpc';
'require uci';
'require ui';
'require view';
'require fs';
async function checkProcess() {
try {
const pidofRes = await fs.exec('/bin/pidof', ['lucky']);
if (pidofRes.code === 0) {
return {
running: true,
pid: pidofRes.stdout.trim()
};
}
} catch (err) {
}
try {
const psRes = await fs.exec('/bin/ps', ['-C', 'lucky', '-o', 'pid=']);
const pid = psRes.stdout.trim();
return {
running: pid !== '',
pid: pid || null
};
} catch (err) {
return { running: false, pid: null };
}
}
const getLuckyConfig = rpc.declare({
object: 'luci.lucky',
method: 'get_Info',
expect: { 'Info': {} }
});
function getServiceStatus() {
return L.resolveDefault(checkProcess(), {}).then(function(res) {
let isRunning = false;
try {
if (res && res.running) {
isRunning = true;
}
} catch (e) {
console.error('Service status error:', e);
}
return isRunning;
}).catch(function(error) {
console.error('Service status check failed:', error);
return false;
});
}
function loadLuckyVer() {
return L.resolveDefault(getLuckyConfig(), {}).then(function(result) {
// console.debug('loadLuckyVer');
return result.Version || 'Unknown';
});
}
function renderStatus(isRunning, webport, safe_url, protocol, version) {
let statusText = isRunning ? _('RUNNING') : _('NOT RUNNING');
let color = isRunning ? 'green' : 'red';
let icon = isRunning ? '✓' : '✗';
let html = String.format(
'<em><span style="color:%s">%s <strong>%s %s - %s</strong></span></em>',
color, icon, _('Lucky'), version, statusText
);
if (isRunning) {
let buttonUrl = String.format('%s//%s:%s/', protocol, window.location.hostname, webport);
if (safe_url && safe_url.trim() !== '') {
buttonUrl = String.format('%s%s/', buttonUrl, safe_url);
}
html += String.format(
'<input class="cbi-button cbi-button-reload" type="button" style="margin-left: 20px" value="%s" onclick="window.open(\'%s\')">',
_('Open Web Interface'),
buttonUrl
);
}
return html;
}
return view.extend({
load: function() {
return Promise.all([
uci.load('lucky')
]);
},
handleResetUser: async function () {
try {
// 检查文件权限
const stat = await fs.stat('/usr/bin/lucky');
const result = await fs.exec('/usr/bin/lucky', ['-rResetUser', '-cd', '/etc/lucky']);
if (result.code === 0) {
alert(_('SUCCESS:') + '\n' + _('Username and password reset successfully to 666'));
}
} catch (error) { }
},
render: function(data) {
let m, s, o;
let webport = uci.get('lucky', 'lucky', 'port') || '16601';
let safeurl = uci.get('lucky', 'lucky', 'safe') || '';
let uci_ssl = uci.get('lucky', 'lucky', 'ssl') || '0';
let protocol = uci_ssl === '1' ? 'https:' : 'http:';
m = new form.Map('lucky', _('Lucky'),
_('ipv4/ipv6 portforward,ddns,reverseproxy proxy,wake on lan,IOT and more,Default username and password 666'));
// 状态显示部分
s = m.section(form.TypedSection);
s.anonymous = true;
s.addremove = false;
s.render = function() {
poll.add(function() {
return Promise.all([
L.resolveDefault(getServiceStatus()),
L.resolveDefault(loadLuckyVer())
]).then(function(results) {
const [isRunning, version] = results;
var view = document.getElementById('service_status');
if (view) {
view.innerHTML = renderStatus(isRunning, webport, safeurl, protocol, version);
}
}).catch(function(error) {
console.error('Poll error:', error);
});
}, 5); // 添加轮询间隔5秒
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
E('div', { id: 'service_status' },
E('p', {}, _('Collecting data...'))
),
E('div', { 'style': 'text-align: right; font-style: italic;' }, [
E('span', {}, [
_('© github '),
E('a', {
'href': 'https://github.com/sirpdboy',
'target': '_blank',
'style': 'text-decoration: none;'
}, 'by sirpdboy')
])
])
]);
};
s = m.section(form.NamedSection, 'lucky', 'lucky');
o = s.option(form.Flag, 'enabled', _('Enable'));
o.default = o.disabled;
o.rmempty = false;
o = s.option(form.Value, 'port', _('Set the Lucky access port'));
o.default = '16601';
o.rmempty = false;
o.datatype = 'port';
o.validate = function(section_id, value) {
if (value < 1 || value > 65535) {
return _('Port must be between 1 and 65535');
}
return true;
};
o = s.option(form.Value, 'safe', _('Safe entrance'),_('Set an installation access path, eg:sirpdboy'));
o.default = '';
o.datatype = 'string';
o = s.option(form.Flag, 'ssl', _('Enable SSL'),_('Encrypt access using HTTPS'));
o.default = '0';
o.rmempty = false;
o = s.option(form.Value, 'delay', _('Delayed Start (seconds)'));
o.default = '60';
o = s.option(form.Button, '_newpassword', _('ResetUser'),
_('Reset account and password to initial values'));
o.inputtitle = _('ResetUser');
o.inputstyle = 'apply';
o.onclick = L.bind(this.handleResetUser, this, data);
return m.render();
}
});
@@ -0,0 +1,78 @@
// 版权 Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-lucky
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require form';
'require poll';
return view.extend({
load: function() {
return uci.load('lucky');
},
checkRunning: function() {
return fs.exec('/bin/pidof', ['lucky']).then(function(pidRes) {
if (pidRes.code === 0) return { isRunning: true };
return fs.exec('/bin/ash', ['-c', 'ps | grep -q "[l]ucky"']).then(function(grepRes) {
return { isRunning: grepRes.code === 0 };
});
});
},
render: function() {
var self = this;
return this.checkRunning().then(function(checkResult) {
var isRunning = checkResult.isRunning;
let webport = uci.get('lucky', 'lucky', 'port') || '16601';
let safe_url = uci.get('lucky', 'lucky', 'safe') || '';
let uci_ssl = uci.get('lucky', 'lucky', 'ssl') || '0';
let protocol = uci_ssl === '1' ? 'https:' : 'http:';
let buttonUrl = String.format('%s//%s:%s/', protocol, window.location.hostname, webport);
if (safe_url && safe_url.trim() !== '') {
buttonUrl = String.format('%s%s/', buttonUrl, safe_url);
}
var container = E('div');
if (!isRunning ) {
var message = _('Lucky Service Not Running');
container.appendChild(E('div', {
style: 'text-align: center; padding: 2em;'
}, [
E('img', {
src: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCI+PHBhdGggZmlsbD0iI2RmMDAwMCIgZD0iTTk0Mi40MjEgMjM0LjYyNGw4MC44MTEtODAuODExLTE1My4wNDUtMTUzLjA0NS04MC44MTEgODAuODExYy03OS45NTctNTEuNjI3LTE3NS4xNDctODEuNTc5LTI3Ny4zNzYtODEuNTc5LTI4Mi43NTIgMC01MTIgMjI5LjI0OC01MTIgNTEyIDAgMTAyLjIyOSAyOS45NTIgMTk3LjQxOSA4MS41NzkgMjc3LjM3NmwtODAuODExIDgwLjgxMSAxNTMuMDQ1IDE1My4wNDUgODAuODExLTgwLjgxMWM3OS45NTcgNTEuNjI3IDE3NS4xNDcgODEuNTc5IDI3Ny4zNzYgODEuNTc5IDI4Mi43NTIgMCA1MTItMjI5LjI0OCA1MTItNTEyIDAtMTAyLjIyOS0yOS45NTItMTk3LjQxOS04MS41NzktMjc3LjM3NnpNMTk0Ljk0NCA1MTJjMC0xNzUuMTA0IDE0MS45NTItMzE3LjA1NiAzMTcuMDU2LTMxNy4wNTYgNDggMCA5My40ODMgMTAuNjY3IDEzNC4yMjkgMjkuNzgxbC00MjEuNTQ3IDQyMS41NDdjLTE5LjA3Mi00MC43ODktMjkuNzM5LTg2LjI3Mi0yOS43MzktMTM0LjI3MnpNNTEyIDgyOS4wNTZjLTQ4IDAtOTMuNDgzLTEwLjY2Ny0xMzQuMjI5LTI5Ljc4MWw0MjEuNTQ3LTQyMS41NDdjMTkuMDcyIDQwLjc4OSAyOS43ODEgODYuMjcyIDI5Ljc4MSAxMzQuMjI5LTAuMDQzIDE3NS4xNDctMTQxLjk5NSAzMTcuMDk5LTMxNy4wOTkgMzE3LjA5OXoiLz48L3N2Zz4=',
style: 'width: 100px; height: 100px; margin-bottom: 1em;'
}),
E('h2', {}, message)
]));
} else {
var iframe = E('iframe', {
src: buttonUrl,
style: 'width: 100%; min-height: 100vh; border: none;'
});
container.appendChild(iframe);
}
poll.add(function() {
return self.checkRunning().then(function(checkResult) {
var newStatus = checkResult.isRunning;
if (newStatus !== isRunning) {
window.location.reload();
}
});
}, 5);
poll.start();
return container;
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});
+63
View File
@@ -0,0 +1,63 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Lucky"
msgstr "Lucky大吉"
msgid "Lucky Control panel"
msgstr "Lucky操作台"
msgid "ipv4/ipv6 portforward,ddns,reverseproxy proxy,wake on lan,IOT and more,Default username and password 666"
msgstr "IPv4/IPv6端口转发,动态域名服务,http/https反向代理,默认用户名密码666.."
msgid "Enable SSL"
msgstr "开启SSL"
msgid "Collecting data..."
msgstr "收集数据..."
msgid "Set the Lucky access port"
msgstr "设置访问端口"
msgid "Base Setting"
msgstr "基本设置"
msgid "Safe entrance"
msgstr "安全入口"
msgid "Port must be between 1 and 65535"
msgstr "端口范围1-65535之间"
msgid "Open Web Interface"
msgstr "打开WEB界面"
msgid "RUNNING"
msgstr "运行中"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "Delayed Start (seconds)"
msgstr "开机延时启动(秒)"
msgid "Username and password reset successfully to 666"
msgstr "重置用户名和密码为666"
msgid "SUCCESS:"
msgstr "执行完成:"
msgid "ResetUser"
msgstr "重置用户密码"
msgid "Reset account and password to initial values"
msgstr "重置帐号和密码为初始值"
msgid "Encrypt access using HTTPS"
msgstr "使用https加密访问"
msgid "Set an installation access path, eg:sirpdboy"
msgstr "设置一个安装访问路径,如:sirpdboy"
msgid "Lucky Service Not Running"
msgstr "Lucky服务未启用"
@@ -0,0 +1,13 @@
#!/bin/sh
[ -f "/etc/config/ucitrack" ] && {
uci -q batch <<-EOF >/dev/null
delete ucitrack.@lucky[-1]
add ucitrack lucky
set ucitrack.@lucky[-1].init=lucky
commit ucitrack
EOF
}
rm -rf /tmp/luci-*
exit 0
@@ -0,0 +1,30 @@
{
"admin/services/lucky": {
"title": "Lucky",
"order": 59,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-lucky" ],
"uci": { "lucky": true }
}
},
"admin/services/lucky/lucky": {
"title": "Lucky Control panel",
"order": 10,
"action": {
"type": "view",
"path": "lucky/lucky"
}
},
"admin/services/lucky/config": {
"title": "Base Setting",
"order": 20,
"action": {
"type": "view",
"path": "lucky/config"
}
}
}
@@ -0,0 +1,29 @@
{
"luci-app-lucky": {
"description": "Grant UCI access for luci-app-lucky",
"read": {
"uci": [ "lucky" ],
"file": {
"/etc/init.d/lucky": [ "exec" ],
"/usr/bin/lucky": [ "exec" ],
"/bin/pidof": [ "exec" ],
"/bin/ps": [ "exec" ],
"/bin/ash": [ "exec" ]
},
"ubus": {
"rc": [ "*" ],
"service": ["list"],
"luci.lucky": [ "*" ]
}
},
"write": {
"ubus": {
"luci.lucky": [ "*" ]
},
"file": {
"/etc/lucky/*.lkcf": ["write"]
},
"uci": ["lucky"]
}
}
}
@@ -0,0 +1,94 @@
#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-lucky
*/
'use strict';
import { access, error, lstat, popen, readfile, writefile } from 'fs';
/* Kanged from ucode/luci */
function shellquote(s) {
return `'${replace(s, "'", "'\\''")}'`;
}
const methods = {
set_ConfInfo: {
args: { key: 'key', value: 'value' },
call: function(req) {
if (!access('/usr/bin/lucky'))
return { status: 255, error: 'lucky not found' };
if (!req.args?.key)
return { status: 255, error: 'missing key parameter' };
const key = shellquote(req.args.key);
const configPath = '/etc/lucky';
let exit_code = 255;
if (req.args.value !== null && req.args.value !== undefined) {
const value = shellquote(req.args.value);
exit_code = system(`/usr/bin/lucky -setconf -key ${key} -value ${value} -cd ${configPath}`);
} else {
// Èç¹ûûÓÐÌṩvalue£¬Ö»ÉèÖÃkey
exit_code = system(`/usr/bin/lucky -setconf -key ${key} -cd ${configPath}`);
}
return { status: exit_code };
}
},
get_baseInfo: {
call: function() {
if (!access('/usr/bin/lucky'))
return { ConfInfo: {}, error: 'lucky not found' };
const configPath = '/etc/lucky';
const fd = popen('/usr/bin/lucky -baseConfInfo -cd /etc/lucky');
if (fd) {
let config = fd.read('all');
fd.close();
if (!config || length(config) === 0)
return { ConfInfo: {}, error: 'empty response' };
try {
config = json(config);
return { ConfInfo: config };
} catch(e) {
return { ConfInfo: {}, error: 'JSON parse error: ' + e };
}
} else {
return { ConfInfo: {}, error: 'failed to execute lucky command' };
}
}
},
get_Info: {
call: function() {
if (!access('/usr/bin/lucky'))
return { Info: {}, error: 'lucky not found' };
const configPath = '/etc/lucky';
const fd = popen('/usr/bin/lucky -info -cd /etc/lucky');
if (fd) {
let config = fd.read('all');
fd.close();
if (!config || length(config) === 0)
return { Info: {}, error: 'empty response' };
try {
config = json(config);
return { Info: config };
} catch(e) {
return { Info: {}, error: 'JSON parse error: ' + e };
}
} else {
return { Info: {}, error: 'failed to execute lucky command' };
}
}
}
};
return { 'luci.lucky': methods };