🍉 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