mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 15:53:56 +08:00
🎉 Sync 2026-10-01 14:45:51
This commit is contained in:
1 parent
f1518a803c
commit
620e59fee5
124 files changed
+21594
-1
No files matched your search
@@ -0,0 +1,118 @@
|
||||
{#
|
||||
Copyright 2026 然后七年 <69092025+zzsj0928@users.noreply.github.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{%
|
||||
import * as fs from 'fs';
|
||||
import { popen } from 'fs';
|
||||
%}
|
||||
|
||||
{%
|
||||
const boardinfo = ubus.call('system', 'board') ?? {};
|
||||
%}
|
||||
|
||||
{%
|
||||
/* 从包数据库读取版本号(支持 apk 和 opkg)。
|
||||
优化:mtime 校验 + /tmp 缓存,避免每次渲染 fork awk。
|
||||
包安装/卸载/升级会更新 db mtime → 缓存自动失效重建,版本零滞后。 */
|
||||
let theme_ver = '';
|
||||
let db_file = '/lib/apk/db/installed';
|
||||
let db_mtime = 0;
|
||||
let st = fs.stat(db_file);
|
||||
if (st && st.type == 'file') {
|
||||
db_mtime = st.mtime;
|
||||
} else {
|
||||
db_file = '/usr/lib/opkg/status';
|
||||
st = fs.stat(db_file);
|
||||
if (st && st.type == 'file')
|
||||
db_mtime = st.mtime;
|
||||
}
|
||||
if (db_mtime > 0) {
|
||||
let cm = match(fs.readfile('/tmp/liquid-version.cache') ?? '', /^(\d+) (.*)$/);
|
||||
if (cm !== null && sprintf('%d', db_mtime) == cm[1])
|
||||
theme_ver = cm[2];
|
||||
}
|
||||
if (theme_ver == '') {
|
||||
let _vf = popen("awk '/^P:luci-theme-liquid$/{f=1;next} f&&/^V:/{print substr($0,3);exit}' " + db_file + " 2>/dev/null", "r");
|
||||
if (_vf) { theme_ver = replace(_vf.read("all"), /\s+/, ""); _vf.close(); }
|
||||
if (theme_ver == '') {
|
||||
_vf = popen("awk '/^Package: luci-theme-liquid$/{f=1;next} f&&/^Version:/{print $2;exit}' /usr/lib/opkg/status 2>/dev/null", "r");
|
||||
if (_vf) { theme_ver = replace(_vf.read("all"), /\s+/, ""); _vf.close(); }
|
||||
}
|
||||
if (theme_ver != '' && db_mtime > 0)
|
||||
fs.writefile('/tmp/liquid-version.cache', sprintf('%d %s', db_mtime, theme_ver));
|
||||
}
|
||||
if (theme_ver == '') theme_ver = '?';
|
||||
|
||||
/* 固件信息:优先读 /etc/os-release 的 FIRMWARE_URL(固件 release 链接)
|
||||
与 OPENWRT_RELEASE(固件版本文本),读不到时回退动态构造。
|
||||
锁屏与登录后 foot 横幅共用。 */
|
||||
let fw_url = '';
|
||||
let fw_text = '';
|
||||
let fw_release = '';
|
||||
let osrel = fs.readfile('/etc/os-release') ?? '';
|
||||
let lines = split(osrel, '\n');
|
||||
for (let i = 0; i < length(lines); i++) {
|
||||
let m1 = match(lines[i], /^FIRMWARE_URL=(.*)$/);
|
||||
if (m1 !== null)
|
||||
fw_url = trim(m1[1], "\"\n\r");
|
||||
let m2 = match(lines[i], /^OPENWRT_RELEASE=(.*)$/);
|
||||
if (m2 !== null)
|
||||
fw_text = trim(m2[1], "\"\n\r");
|
||||
}
|
||||
if (fw_url != '')
|
||||
fw_release = fw_url;
|
||||
else {
|
||||
let fw_m = match(version.distversion ?? '', /OpenWrt[ \t]+([0-9]+\.[0-9]+(\.[0-9]+)?)/);
|
||||
fw_release = (fw_m !== null)
|
||||
? sprintf('https://downloads.openwrt.org/releases/%s/', fw_m[1])
|
||||
: 'https://github.com/openwrt/openwrt/releases';
|
||||
}
|
||||
if (fw_text == '')
|
||||
fw_text = sprintf('%s %s', version.distversion ?? '', version.distrevision ?? '');
|
||||
|
||||
/* LuCI release 链接:luciversion 为纯版本号(如 23.05.3)时链接对应
|
||||
release tag,否则(git 快照版本)回退官方 release 页 */
|
||||
let luci_m = match(version.luciversion ?? '', /^([0-9]+\.[0-9]+(\.[0-9]+)?)$/);
|
||||
let luci_release = (luci_m !== null)
|
||||
? sprintf('https://github.com/openwrt/luci/releases/tag/v%s', luci_m[1])
|
||||
: 'https://github.com/openwrt/luci/releases';
|
||||
%}
|
||||
|
||||
{% if (!blank_page): %}
|
||||
<div class="luci-foot-spacer"></div>
|
||||
</div><!-- /#maincontent -->
|
||||
</div><!-- /#maincontainer -->
|
||||
{% endif %}
|
||||
|
||||
{% if (blank_page): %}
|
||||
<p class="luci">
|
||||
<a href="https://github.com/zzsj0928/luci-theme-liquid" target="_blank" rel="noopener">Liquid Theme {{ theme_ver }}</a>
|
||||
<span class="luci-sep">·</span>
|
||||
<a href="{{ fw_release }}" target="_blank" rel="noopener">{{ fw_text }}</a>
|
||||
<span class="luci-sep">·</span>
|
||||
<span>Kernel: {{ boardinfo.kernel ?? '?' }}</span>
|
||||
<span class="luci-sep">·</span>
|
||||
<a href="{{ luci_release }}" target="_blank" rel="noopener">{{ version.luciname }} {{ version.luciversion }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if (!blank_page): %}
|
||||
<p class="luci luci-foot">
|
||||
<a class="liquid-version-link luci-theme-ver" data-ver="{{ theme_ver }}" href="https://github.com/zzsj0928/luci-theme-liquid" target="_blank" rel="noopener">Liquid Theme {{ theme_ver }}</a>
|
||||
<span class="luci-sep luci-sep-fw">·</span>
|
||||
<a class="luci-fw" href="{{ fw_release }}" target="_blank" rel="noopener">{{ fw_text }}</a>
|
||||
<span class="luci-sep">·</span>
|
||||
<span>Kernel: {{ boardinfo.kernel ?? '?' }}</span>
|
||||
<span class="luci-sep">·</span>
|
||||
<a href="{{ luci_release }}" target="_blank" rel="noopener">{{ version.luciname }} {{ version.luciversion }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if (!blank_page): %}
|
||||
<script>L.require('menu-liquid')</script>
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="{% print(media); %}/main.js?v={% print(theme_ver); %}"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,252 @@
|
||||
{#
|
||||
Copyright 2026 然后七年 <69092025+zzsj0928@users.noreply.github.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{%
|
||||
import * as fs from 'fs';
|
||||
import { popen } from 'fs';
|
||||
import { getuid, getspnam } from 'luci.core';
|
||||
|
||||
const boardinfo = ubus.call('system', 'board') ?? {};
|
||||
|
||||
/* 从包数据库读取版本号(支持 apk 和 opkg)。
|
||||
优化:mtime 校验 + /tmp 缓存,避免每次渲染 fork awk。
|
||||
包安装/卸载/升级会更新 db mtime → 缓存自动失效重建,版本零滞后。 */
|
||||
let theme_ver = '';
|
||||
let db_file = '/lib/apk/db/installed';
|
||||
let db_mtime = 0;
|
||||
let st = fs.stat(db_file);
|
||||
if (st && st.type == 'file') {
|
||||
db_mtime = st.mtime;
|
||||
} else {
|
||||
db_file = '/usr/lib/opkg/status';
|
||||
st = fs.stat(db_file);
|
||||
if (st && st.type == 'file')
|
||||
db_mtime = st.mtime;
|
||||
}
|
||||
if (db_mtime > 0) {
|
||||
let cm = match(fs.readfile('/tmp/liquid-version.cache') ?? '', /^(\d+) (.*)$/);
|
||||
if (cm !== null && sprintf('%d', db_mtime) == cm[1])
|
||||
theme_ver = cm[2];
|
||||
}
|
||||
if (theme_ver == '') {
|
||||
let f = popen("awk '/^P:luci-theme-liquid$/{f=1;next} f&&/^V:/{print substr($0,3);exit}' " + db_file + " 2>/dev/null", "r");
|
||||
if (f) { theme_ver = replace(f.read("all"), /\s+/, ""); f.close(); }
|
||||
if (theme_ver == '') {
|
||||
f = popen("awk '/^Package: luci-theme-liquid$/{f=1;next} f&&/^Version:/{print $2;exit}' /usr/lib/opkg/status 2>/dev/null", "r");
|
||||
if (f) { theme_ver = replace(f.read("all"), /\s+/, ""); f.close(); }
|
||||
}
|
||||
if (theme_ver != '' && db_mtime > 0)
|
||||
fs.writefile('/tmp/liquid-version.cache', sprintf('%d %s', db_mtime, theme_ver));
|
||||
}
|
||||
if (theme_ver == '') theme_ver = '?';
|
||||
|
||||
http.prepare_content('text/html; charset=UTF-8');
|
||||
|
||||
let ptitle = (node ?? dispatched)?.title;
|
||||
|
||||
/* ---- 主题配置(uci /etc/config/liquid,换客户端保留) ---- */
|
||||
let cfg_mode = 'auto';
|
||||
let cfg_accent = 'blue';
|
||||
let cfg_accent_custom = '';
|
||||
let cfg_bing = '0';
|
||||
let cfg_glass_opacity = '';
|
||||
let cfg = fs.readfile('/etc/config/liquid') ?? '';
|
||||
let cm = match(cfg, /option[ \t]+mode[ \t]+'?([^' \t\n]+)'?/);
|
||||
if (cm !== null)
|
||||
cfg_mode = cm[1];
|
||||
let ca = match(cfg, /option[ \t]+accent[ \t]+'?([^' \t\n]+)'?/);
|
||||
if (ca !== null)
|
||||
cfg_accent = ca[1];
|
||||
let cc = match(cfg, /option[ \t]+accent_custom[ \t]+'?([^' \t\n]+)'?/);
|
||||
if (cc !== null)
|
||||
cfg_accent_custom = cc[1];
|
||||
let cb = match(cfg, /option[ \t]+bing[ \t]+'?([^' \t\n]+)'?/);
|
||||
if (cb !== null)
|
||||
cfg_bing = cb[1];
|
||||
let co = match(cfg, /option[ \t]+glass_opacity[ \t]+'?([^' \t\n]+)'?/);
|
||||
if (co !== null)
|
||||
cfg_glass_opacity = co[1];
|
||||
|
||||
/* ---- Bing 每日壁纸:缓存到各设备自己的 /tmp/liquid/,
|
||||
软链 /www/liquid-cache 供 http 访问(/www 为 overlay 可写)。
|
||||
system() 只透传 stdout(返回退出码),因此用"写临时文件 +
|
||||
fs.readfile"获取 wget 输出 ---- */
|
||||
let bing_style = '';
|
||||
let bing_ok = '0';
|
||||
let accent_custom_style = '';
|
||||
let glass_opacity_style = '';
|
||||
if (cfg_accent == 'custom' && cfg_accent_custom != '')
|
||||
accent_custom_style = sprintf(':root{--accent-custom:%s;}', cfg_accent_custom);
|
||||
if (cfg_glass_opacity != '') {
|
||||
let op = int(cfg_glass_opacity, 10);
|
||||
if (op < 0) op = 0;
|
||||
if (op > 100) op = 100;
|
||||
/* 亮/暗参考 alpha 不同,JS 会根据 data-darkmode 动态修正,
|
||||
此处只注入 slider 值供 JS 首帧读取 */
|
||||
let def_light = 42;
|
||||
let def_dark = 40;
|
||||
let f_light = def_light > 0 ? sprintf('%.2f', op / def_light) : '1.00';
|
||||
let f_dark = def_dark > 0 ? sprintf('%.2f', op / def_dark) : '1.00';
|
||||
glass_opacity_style = sprintf(':root[data-darkmode="false"]{--glass-opacity:%s;}:root[data-darkmode="true"]{--glass-opacity:%s;}', f_light, f_dark);
|
||||
}
|
||||
|
||||
if (cfg_bing == '1') {
|
||||
system('mkdir -p /tmp/liquid && ln -sfn /tmp/liquid /www/liquid-cache && date +%Y%m%d > /tmp/liquid-date.txt');
|
||||
let today = trim(fs.readfile('/tmp/liquid-date.txt') ?? '');
|
||||
if (today != '') {
|
||||
let bing_file = sprintf('/tmp/liquid/bing-%s.jpg', today);
|
||||
if (fs.stat(bing_file)) {
|
||||
bing_style = sprintf(":root{--liquid-bing:url('/liquid-cache/bing-%s.jpg');}", today);
|
||||
bing_ok = '1';
|
||||
/* 无论今天壁纸是新下载还是已缓存,只要确认存在,
|
||||
就清理 /tmp/liquid/ 下所有历史壁纸,只保留今天的 */
|
||||
system(sprintf('ls /tmp/liquid/bing-*.jpg 2>/dev/null | grep -v "bing-%s.jpg" | xargs rm -f 2>/dev/null', today));
|
||||
} else {
|
||||
system('wget -qO /tmp/liquid-bing-api.json -T 8 "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"');
|
||||
let api = fs.readfile('/tmp/liquid-bing-api.json') ?? '';
|
||||
if (api != '') {
|
||||
let j = json(api);
|
||||
if (j && j.images && j.images[0] && j.images[0].urlbase) {
|
||||
let u = sprintf('https://www.bing.com%s_1920x1080.jpg', j.images[0].urlbase);
|
||||
system(sprintf('wget -qO "%s" -T 20 "%s"', bing_file, u));
|
||||
if (fs.stat(bing_file)) {
|
||||
bing_style = sprintf(":root{--liquid-bing:url('/liquid-cache/bing-%s.jpg');}", today);
|
||||
bing_ok = '1';
|
||||
/* 新壁纸下载成功后清理 /tmp/liquid/ 下所有旧壁纸,
|
||||
只保留今天的文件,防止壁纸随时间堆积占满 /tmp */
|
||||
system(sprintf('ls /tmp/liquid/bing-*.jpg 2>/dev/null | grep -v "bing-%s.jpg" | xargs rm -f 2>/dev/null', today));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-%}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ dispatcher.lang }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="darkreader-lock">
|
||||
<!-- 浏览器工具栏配色:初值=底部 foot 玻璃横幅的渲染实色(亮 #f8f9fb /
|
||||
暗 #151c2b,即 --glass-bg-strong 叠 --background-color-medium 的合成色),
|
||||
让移动端浏览器工具栏与页面上下玻璃栏融为一体。首绘后由 main.js
|
||||
syncThemeColor() 用 canvas 实时合成精确值并跟随明暗切换 -->
|
||||
<meta name="theme-color" content="#f8f9fb" />
|
||||
{% if (glass_opacity_style != ''): %}<style>{{ glass_opacity_style }}</style>{% endif %}
|
||||
{% if (bing_style != ''): %}<style>{{ bing_style }}</style>{% endif %}
|
||||
{% if (accent_custom_style != ''): %}<style>{{ accent_custom_style }}</style>{% endif %}
|
||||
<script>
|
||||
/* Liquid theme: apply uci-persisted light/dark/auto mode + accent before CSS paints.
|
||||
uci(服务端模板读取)优先:其他客户端修改后本机刷新即生效;
|
||||
仅当 uci 未配置时回退 localStorage。锁屏页同样按 uci 显示(只读)。 */
|
||||
(function () {
|
||||
try {
|
||||
if ('scrollRestoration' in history)
|
||||
history.scrollRestoration = 'manual';
|
||||
var m = "{{ cfg_mode }}";
|
||||
var a = "{{ cfg_accent }}";
|
||||
if (!m)
|
||||
m = localStorage.getItem('liquid-theme-mode') || 'auto';
|
||||
if (!a)
|
||||
a = localStorage.getItem('liquid-accent') || 'blue';
|
||||
var dark = (m == 'dark') || ((m != 'light') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.setAttribute('data-darkmode', dark ? 'true' : 'false');
|
||||
document.documentElement.setAttribute('data-liquid-mode', m);
|
||||
document.documentElement.setAttribute('data-accent', a);
|
||||
/* 工具栏色随明暗:foot 玻璃渲染实色(亮/暗两套),main.js 之后会
|
||||
用 canvas 合成精确值覆盖,此处只给首绘一个接近值避免闪默认色 */
|
||||
var tc = document.querySelector('meta[name="theme-color"]');
|
||||
if (tc)
|
||||
tc.setAttribute('content', dark ? '#151c2b' : '#f8f9fb');
|
||||
/* 明暗守护(head 阶段即安装):OpenClash 等第三方脚本会在 body
|
||||
解析期间覆写 <html data-darkmode>(其 common.js 按背景亮度
|
||||
判定,加载早期 body 背景未算出时误判为暗色),而主题 main.js
|
||||
在 footer 才加载,浏览器可能在解析到 footer 前就绘制出暗色帧
|
||||
——表现为"进 OpenClash 先闪暗再纠正"。此处在 head 就装
|
||||
监视器:任何与主题有效模式不符的外部写入都在微任务里立即
|
||||
还原(同步 data-darkmode 与 color-scheme),首帧绘制前完成,
|
||||
彻底杜绝闪暗。 */
|
||||
if (window.MutationObserver) {
|
||||
var _gRoot = document.documentElement;
|
||||
var _gMql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
||||
var _gMeta = document.querySelector('meta[name="color-scheme"]');
|
||||
var _gSelf = false;
|
||||
function _gWant() {
|
||||
var mm = _gRoot.getAttribute('data-liquid-mode') || m;
|
||||
return ((mm == 'dark') || ((mm != 'light') && _gMql && _gMql.matches)) ? 'true' : 'false';
|
||||
}
|
||||
new MutationObserver(function () {
|
||||
if (_gSelf)
|
||||
return;
|
||||
var want = _gWant();
|
||||
if (_gRoot.getAttribute('data-darkmode') !== want) {
|
||||
_gSelf = true;
|
||||
_gRoot.setAttribute('data-darkmode', want);
|
||||
_gSelf = false;
|
||||
}
|
||||
if (_gMeta && _gMeta.content !== (want == 'true' ? 'dark' : 'light'))
|
||||
_gMeta.content = (want == 'true' ? 'dark' : 'light');
|
||||
}).observe(_gRoot, { attributes: true, attributeFilter: ['data-darkmode'] });
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="{% print(media); %}/cascade.css?v={% print(theme_ver); %}" />
|
||||
<link rel="icon" href="{{ media }}/logo.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="{{ media }}/logo.png" />
|
||||
<script type="text/javascript" src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
|
||||
<script type="text/javascript" src="{{ resource }}/cbi.js"></script>
|
||||
<title>{{ striptags(`${boardinfo.hostname ?? '?'}${ptitle ? ` | ${_(ptitle)}` : ''}`) }}</title>
|
||||
{% if (css): %}
|
||||
<style title="text/css">{{ css }}</style>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body class="lang_{{ dispatcher.lang }}{% if (blank_page): %} liquid-login{% endif %}" data-page="{{ entityencode(join('-', ctx.request_path), true) }}" data-hostname="{{ striptags(boardinfo.hostname ?? '') }}" data-liquid-mode="{{ cfg_mode }}" data-liquid-accent="{{ cfg_accent }}" data-liquid-bing="{{ cfg_bing }}" data-liquid-bing-ok="{{ bing_ok }}" data-liquid-accent-custom="{{ cfg_accent_custom }}" data-liquid-glass-opacity="{{ cfg_glass_opacity }}">
|
||||
|
||||
<p class="skiplink">
|
||||
<span id="skiplink1"><a href="#navigation">{{ _('Skip to navigation') }}</a></span>
|
||||
<span id="skiplink2"><a href="#content">{{ _('Skip to content') }}</a></span>
|
||||
</p>
|
||||
|
||||
{% if (!blank_page): %}
|
||||
<div id="menubar">
|
||||
<h2 class="navigation"><a id="navigation" name="navigation">{{ _('Navigation') }}</a></h2>
|
||||
|
||||
<span class="hostname"><a href="/"><svg class="liquid-logo" viewBox="0 0 64 68" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><defs><linearGradient id="liquid-lg-top" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="var(--primary-color-high)" stop-opacity="0.85"/><stop offset="0.5" stop-color="var(--primary-color-high)" stop-opacity="0.4"/><stop offset="1" stop-color="var(--primary-color-low)" stop-opacity="0.92"/></linearGradient></defs><path d="M32 3 C46 20 57 30 57 42 a25 25 0 0 1 -50 0 C7 30 18 20 32 3 Z" fill="url(#liquid-lg-top)" stroke="rgba(255,255,255,0.7)" stroke-width="1.5"/><ellipse cx="23" cy="39" rx="9.5" ry="6" fill="#ffffff" opacity="0.6"/></svg>{{ striptags(boardinfo.hostname ?? '?') }}</a></span>
|
||||
<span class="distversion">{{ version.distversion }} {{ version.distrevision }}</span>
|
||||
<span id="indicators"></span>
|
||||
</div>
|
||||
|
||||
<div id="modemenu" style="display:none"></div>
|
||||
|
||||
<div id="maincontainer">
|
||||
<div id="mainmenu"></div>
|
||||
|
||||
<div id="maincontent">
|
||||
{% if (getuid() == 0 && getspnam('root')?.pwdp === '' && join('-', ctx.request_path) != 'admin-system-admin'): %}
|
||||
<div class="alert-message warning">
|
||||
<h4>{{ _('No password set!') }}</h4>
|
||||
<p>{{ _('There is no password set on this router. Please configure a root password to protect the web interface.') }}</p>
|
||||
{% if (dispatcher.lookup("admin/system/admin")): %}
|
||||
<div class="right"><a class="btn" href="{{ dispatcher.build_url("admin/system/admin") }}">{{ _('Go to password configuration...') }}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if (boardinfo.rootfs_type == "initramfs"): %}
|
||||
<div class="alert-message warning">
|
||||
<h4>{{ _('System running in recovery (initramfs) mode.') }}</h4>
|
||||
<p>{{ _('No changes to settings will be stored and are lost after rebooting. This mode should only be used to install a firmware upgrade') }}</p>
|
||||
{% if (dispatcher.lookup("admin/system/flash")): %}
|
||||
<div class="right"><a class="btn" href="{{ dispatcher.build_url("admin/system/flash") }}">{{ _('Go to firmware upgrade...') }}</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="tabmenu" style="display:none"></div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,122 @@
|
||||
{#
|
||||
Copyright 2026 然后七年 <69092025+zzsj0928@users.noreply.github.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{% include('header', { blank_page: true }) %}
|
||||
|
||||
{# 完全静态登录页:form 直接渲染在 DOM 中,无 hidden/opacity、无 DOM 移动、
|
||||
不依赖 LuCI 的 #modal_overlay/ui.showModal。CSS (#liquid-login) 负责
|
||||
全屏遮罩 + 居中玻璃卡片。密码管理器(Bitwarden 等)任何时机扫描
|
||||
都能看到完整字段,无竞态。 #}
|
||||
|
||||
<div id="liquid-login">
|
||||
<div class="liquid-login-stack">
|
||||
<div id="liquid-login-capsules"></div>
|
||||
<div class="modal login">
|
||||
<div class="liquid-logo">
|
||||
<svg viewBox="0 0 64 68" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<defs>
|
||||
<!-- 水滴形裁剪路径(CSS clip-path 引用) -->
|
||||
<clipPath id="liquid-drop-clip" clipPathUnits="objectBoundingBox">
|
||||
<path d="M0.5,0 C0.72,0.29 0.89,0.44 0.89,0.62 A0.39,0.39 0 0,1 0.11,0.62 C0.11,0.44 0.28,0.29 0.5,0 Z"/>
|
||||
</clipPath>
|
||||
<!-- accent-glass-soft 风格渐变:左下主题色 → 右上白色半透明 -->
|
||||
<linearGradient id="liquid-lg-top" x1="0" y1="1" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="var(--primary-color-low)" stop-opacity="0.85"/>
|
||||
<stop offset="35%" stop-color="var(--primary-color-medium)" stop-opacity="0.55"/>
|
||||
<stop offset="100%" stop-color="rgba(255,255,255,0.72)"/>
|
||||
</linearGradient>
|
||||
<!-- 左上角高光条(模拟玻璃折射亮线) -->
|
||||
<linearGradient id="liquid-lg-shine" x1="0" y1="0" x2="0.5" y2="0.8">
|
||||
<stop offset="0%" stop-color="white" stop-opacity="0.7"/>
|
||||
<stop offset="50%" stop-color="white" stop-opacity="0.15"/>
|
||||
<stop offset="100%" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M32 3 C46 20 57 30 57 42 a25 25 0 0 1 -50 0 C7 30 18 20 32 3 Z" fill="url(#liquid-lg-top)"/>
|
||||
<path d="M32 3 C46 20 57 30 57 42 a25 25 0 0 1 -50 0 C7 30 18 20 32 3 Z" fill="url(#liquid-lg-shine)"/>
|
||||
<ellipse cx="22" cy="40" rx="10" ry="6.5" fill="white" opacity="0.18"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4>{{ _('Authorization Required') }}</h4>
|
||||
<form method="post" action="/cgi-bin/luci/" class="cbi-map" onsubmit="if(!this.luci_password.value){this.luci_password.focus();return false;}">
|
||||
<div class="cbi-section">
|
||||
<div class="cbi-section-node">
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title" for="luci_username">{{ _('Username') }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-user" name="luci_username" id="luci_username" type="text" autocomplete="username" value="{{ entityencode(duser, true) }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title" for="luci_password">{{ _('Password') }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-password" name="luci_password" id="luci_password" type="password" autocomplete="current-password">
|
||||
</div>
|
||||
</div>
|
||||
{% if (auth_fields): %}
|
||||
{% for (let field in auth_fields): %}
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title" for="{{ field.name }}">{{ _(field.label ?? field.name) }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input
|
||||
name="{{ field.name }}"
|
||||
id="{{ field.name }}"
|
||||
type="{{ field.type ?? 'text' }}"
|
||||
{% if (field.placeholder): %}placeholder="{{ field.placeholder }}"{% endif %}
|
||||
{% if (field.inputmode): %}inputmode="{{ field.inputmode }}"{% endif %}
|
||||
{% if (field.pattern): %}pattern="{{ field.pattern }}"{% endif %}
|
||||
{% if (field.maxlength): %}maxlength="{{ field.maxlength }}"{% endif %}
|
||||
{% if (field.autocomplete): %}autocomplete="{{ field.autocomplete }}"{% endif %}
|
||||
{% if (field.required): %}required{% endif %}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if (auth_html): %}
|
||||
<div class="cbi-value">
|
||||
{{ auth_html }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (auth_assets): %}
|
||||
{% for (let asset in auth_assets): %}
|
||||
{% if (asset.type == 'script'): %}
|
||||
<script src="{{ asset.src }}"></script>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<hr>
|
||||
<div class="cbi-value">
|
||||
<div class="cbi-value-field">
|
||||
<button type="submit" class="btn cbi-button-positive important">{{ _('Log in') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if (auth_message): %}
|
||||
<div class="alert-message{% if (auth_plugin): %} warning{% endif %}">
|
||||
{{ auth_message }}
|
||||
</div>
|
||||
{% elif (fuser): %}
|
||||
<div class="alert-message error">
|
||||
{{ _('Invalid username and/or password! Please try again.') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view">
|
||||
<div class="spinning">{{ _('Loading view…') }}</div>
|
||||
<script>
|
||||
L.require('ui').then(function(ui) {
|
||||
ui.instantiateView('liquid.sysauth');
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% include('footer', { blank_page: true }) %}
|
||||
Reference in new issue
Block a user