🍉 Sync 2025-12-11 00:15:18
This commit is contained in:
12
luci-mod-istorenext/Makefile
Normal file
12
luci-mod-istorenext/Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=iStoreNext
|
||||||
|
LUCI_DESCRIPTION:=LuCI module for iStoreNext
|
||||||
|
PKG_VERSION:=0.0.2
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
LUCI_DEPENDS:=+luci-nginxer
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
174
luci-mod-istorenext/htdocs/istorenext-login.html
Normal file
174
luci-mod-istorenext/htdocs/istorenext-login.html
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>登录</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header h1 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #fee;
|
||||||
|
color: #c33;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-header">
|
||||||
|
<h1>登录</h1>
|
||||||
|
<p>欢迎回来,请输入您的凭证</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="errorMessage" class="error-message"></div>
|
||||||
|
|
||||||
|
<form action="?istorenextlogin=1" method="post" id="loginForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="luci_username"
|
||||||
|
required
|
||||||
|
autocomplete="username"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
value="root">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="luci_password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
placeholder="请输入密码">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="login-button">登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 客户端表单验证
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||||
|
const username = document.getElementById('username').value.trim();
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
e.preventDefault();
|
||||||
|
errorMessage.textContent = '请输入用户名';
|
||||||
|
errorMessage.classList.add('show');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除错误消息
|
||||||
|
errorMessage.classList.remove('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输入时清除错误消息
|
||||||
|
const inputs = document.querySelectorAll('input');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
document.getElementById('errorMessage').classList.remove('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
116
luci-mod-istorenext/root-demo/demo/cgi-bin/luci/istorenext/cgi-bin/logon
Executable file
116
luci-mod-istorenext/root-demo/demo/cgi-bin/luci/istorenext/cgi-bin/logon
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env ucode
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { connect } from 'ubus';
|
||||||
|
import request from 'luci.http';
|
||||||
|
|
||||||
|
let ubus = connect();
|
||||||
|
let http;
|
||||||
|
|
||||||
|
function session_retrieve(sid, allowed_users) {
|
||||||
|
let sdat = ubus.call("session", "get", { ubus_rpc_session: sid });
|
||||||
|
if (type(sdat?.values?.token) == 'string' &&
|
||||||
|
(!length(allowed_users) || sdat?.values?.username in allowed_users)) {
|
||||||
|
// uci:set_session_id(sid)
|
||||||
|
return {
|
||||||
|
sid,
|
||||||
|
data: sdat.values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_authentication(method) {
|
||||||
|
let m = match(method, /^([[:alpha:]]+):(.+)$/);
|
||||||
|
let sid;
|
||||||
|
|
||||||
|
switch (m?.[1]) {
|
||||||
|
case 'cookie':
|
||||||
|
sid = http.getcookie(m[2]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'param':
|
||||||
|
sid = http.formvalue(m[2]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'query':
|
||||||
|
sid = http.formvalue(m[2], true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sid ? session_retrieve(sid) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_authenticated(auth) {
|
||||||
|
for (let method in auth?.methods) {
|
||||||
|
let session = check_authentication(method);
|
||||||
|
|
||||||
|
if (session)
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input_bufsize = 4096;
|
||||||
|
let input_available = +getenv('CONTENT_LENGTH') || 0;
|
||||||
|
|
||||||
|
import { stdin, stdout } from 'fs';
|
||||||
|
|
||||||
|
function read(len) {
|
||||||
|
if (input_available == 0) {
|
||||||
|
stdin.close();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = stdin.read(min(input_available, len ?? input_bufsize, input_bufsize));
|
||||||
|
|
||||||
|
if (chunk == null) {
|
||||||
|
input_available = 0;
|
||||||
|
stdin.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input_available -= length(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(data) {
|
||||||
|
return stdout.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
http = request(getenv(), read, write);
|
||||||
|
|
||||||
|
import { open } from 'fs';
|
||||||
|
function read_jsonfile(path, defval) {
|
||||||
|
let rv;
|
||||||
|
|
||||||
|
try {
|
||||||
|
rv = json(open(path, "r"));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
rv = defval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
let luci_base_menu = read_jsonfile("/usr/share/luci/menu.d/luci-base.json");
|
||||||
|
|
||||||
|
let session = is_authenticated(luci_base_menu["admin"].auth);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
http.status(403, 'Forbidden');
|
||||||
|
http.header('X-LuCI-Login-Required', 'yes');
|
||||||
|
http.prepare_content('text/html; charset=UTF-8');
|
||||||
|
http.write('<html><body><h1>403 Forbidden</h1><p>This page should be overridden by nginx</p></body></html>');
|
||||||
|
} else {
|
||||||
|
http.status(200, 'OK');
|
||||||
|
http.prepare_content('application/json; charset=UTF-8');
|
||||||
|
http.write_json(session);
|
||||||
|
}
|
||||||
|
http.close();
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<h1>Hello world!</h1>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/cgi-bin/luci/istorenext/cgi-bin/logon">Check logon</a></li>
|
||||||
|
<li><a href="/cgi-bin/luci/admin">LuCI</a></li>
|
||||||
|
</ul>
|
||||||
1
luci-mod-istorenext/root-demo/istorenext-demo.sh
Executable file
1
luci-mod-istorenext/root-demo/istorenext-demo.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
uhttpd -f -h /demo -r Test -x /cgi-bin/luci/istorenext/cgi-bin -u /ubus -t 360 -T 30 -k 20 -A 1 -n 50 -N 100 -R -p 0.0.0.0:8080
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
map $request_method:$query_string $login_request {
|
||||||
|
default 0;
|
||||||
|
~^POST:istorenextlogin=1$ 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
proxy_intercept_errors on;
|
||||||
|
uwsgi_intercept_errors on;
|
||||||
|
|
||||||
|
error_page 403 /istorenext-login.html;
|
||||||
|
|
||||||
|
location /cgi-bin/luci/istorenext {
|
||||||
|
include uwsgi_params;
|
||||||
|
|
||||||
|
uwsgi_param SERVER_ADDR $server_addr;
|
||||||
|
uwsgi_modifier1 9;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
if ($login_request = 0) {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
}
|
||||||
|
if ($login_request = 1) {
|
||||||
|
uwsgi_pass unix:////var/run/luci-webui.socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
luci-mod-istorenext/root/etc/uci-defaults/50_luci-istorenext
Executable file
5
luci-mod-istorenext/root/etc/uci-defaults/50_luci-istorenext
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
rm -f /tmp/luci-indexcache
|
||||||
|
/etc/init.d/nginx reload 2>/dev/null
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<%
|
||||||
|
local url=luci.http.getenv("REQUEST_URI")
|
||||||
|
local prefix="/cgi-bin/luci/istorenext"
|
||||||
|
|
||||||
|
if string.sub(url, 1, #prefix) == prefix then
|
||||||
|
-%>
|
||||||
|
<%=url%>
|
||||||
|
<% else
|
||||||
|
luci.http.redirect(prefix)
|
||||||
|
end %>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"istorenext": {
|
||||||
|
"order": 0,
|
||||||
|
"leaf": true,
|
||||||
|
"action": {
|
||||||
|
"type": "template",
|
||||||
|
"path": "istorenext/index"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||||
|
"login": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
luci-mod-istorenext/test.conf
Normal file
70
luci-mod-istorenext/test.conf
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
user root;
|
||||||
|
|
||||||
|
include module.d/*.module;
|
||||||
|
|
||||||
|
events {}
|
||||||
|
|
||||||
|
http {
|
||||||
|
access_log off;
|
||||||
|
log_format openwrt
|
||||||
|
'$request_method $scheme://$host$request_uri => $status'
|
||||||
|
' (${body_bytes_sent}B in ${request_time}s) <- $http_referer';
|
||||||
|
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
client_max_body_size 256M;
|
||||||
|
large_client_header_buffers 2 1k;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
|
||||||
|
root /www;
|
||||||
|
|
||||||
|
map $request_method:$query_string $login_request {
|
||||||
|
default 0;
|
||||||
|
~^POST:istorenextlogin=1$ 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server { #see uci show 'nginx._redirect2ssl'
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name _redirect2ssl;
|
||||||
|
include restrict_locally;
|
||||||
|
|
||||||
|
proxy_intercept_errors on;
|
||||||
|
uwsgi_intercept_errors on;
|
||||||
|
|
||||||
|
error_page 403 /istorenext-login.html;
|
||||||
|
|
||||||
|
location /cgi-bin/luci/istorenext {
|
||||||
|
include uwsgi_params;
|
||||||
|
|
||||||
|
uwsgi_param SERVER_ADDR $server_addr;
|
||||||
|
uwsgi_modifier1 9;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
if ($login_request = 0) {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
}
|
||||||
|
if ($login_request = 1) {
|
||||||
|
uwsgi_pass unix:////var/run/luci-webui.socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# configuration file /etc/nginx/conf.d/luci.locations:
|
||||||
|
location /cgi-bin/luci {
|
||||||
|
index index.html;
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_param SERVER_ADDR $server_addr;
|
||||||
|
uwsgi_modifier1 9;
|
||||||
|
uwsgi_pass unix:////var/run/luci-webui.socket;
|
||||||
|
}
|
||||||
|
access_log off; # logd openwrt;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user