🐶 Sync 2025-11-02 14:26:26

This commit is contained in:
actions-user
2025-11-02 14:26:26 +08:00
parent 64bcc56c2a
commit ac011db799
1557 changed files with 746465 additions and 0 deletions

23
luci-theme-kucat/Makefile Normal file
View File

@@ -0,0 +1,23 @@
#
# Copyright (C) 2019-2025 The Sirpdboy Team <herboy2008@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
THEME_NAME:=kucat
THEME_TITLE:=Kucat Theme
PKG_NAME:=luci-theme-$(THEME_NAME)
LUCI_TITLE:=Kucat Theme by sirpdboy
LUCI_DEPENDS:=
PKG_VERSION:=2.7.3
PKG_RELEASE:=20251028
define Package/luci-theme-$(THEME_NAME)/conffiles
/www/luci-static/resources/background/
/www/luci-static/kucat/img/
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/luci-static/kucat/img/logo70.png"/>
<square150x150logo src="/luci-static/kucat/img/logo150.png"/>
<wide310x150logo src="/luci-static/kucat/img/logow310x150.png"/>
<square310x310logo src="/luci-static/kucat/img/logo310.png"/>
<square512x512logo src="/luci-static/kucat/img/logo512.png"/>
<TileColor>#1a1a1a</TileColor>
<TileImage src="logo.png"/>
</tile>
<notification>
<polling-uri src="/live-tile-notifications"/>
<frequency>30</frequency>
</notification>
</msapplication>
</browserconfig>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@

@supports (-webkit-touch-callout: none) {
body {
-webkit-touch-callout: none;
padding: env(safe-area-inset-top) env(safe-area-inset-right)
env(safe-area-inset-bottom) env(safe-area-inset-left);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,42 @@
// thanks for sirpdboy Wich <herboy2008@gmail.com> footer差补代码
var winHeight = window.innerHeight;
function debounce(func, delay) {
let timeoutId;
return function() {
clearTimeout(timeoutId);
timeoutId = setTimeout(func, delay);
};
}
function adjustLayout() {
var currentHeight = window.innerHeight;
var winWidth = window.innerWidth;
var footer = document.querySelector('footer');
var footerHeight = footer ? footer.offsetHeight : 0;
var footerRect = footer ? footer.getBoundingClientRect() : null;
var footerBottomPos = footerRect ? footerRect.bottom : 0;
var spaceBelowFooter = currentHeight - footerBottomPos;
if (winWidth < 768) {
var keyboardHeight = currentHeight - winHeight;
document.querySelectorAll('.footend').forEach(function(element) {
element.style.bottom = (keyboardHeight + 80) + 'px';
});
}
document.querySelectorAll('.footend').forEach(function(element) {
if (spaceBelowFooter < 0) {
element.style.paddingBottom = '100px';
} else {
element.style.paddingBottom = '';
}
});
}
adjustLayout();
window.addEventListener('resize', debounce(adjustLayout, 200));

View File

@@ -0,0 +1,87 @@
/* <![CDATA[ */
async function getUci() {
try {
const response = await fetch("/cgi-bin/luci/api/get");
if (!response.ok) throw new Error("Network error");
return await response.json();
} catch (error) {
console.error("Failed to fetch theme config:", error);
return {
success: false,
bgqs: "0",
primaryrgbm: "45,102,147",
primaryrgbmts: "0",
mode: 'light'
};
}
}
function getTimeTheme() {
const hour = new Date().getHours();
return (hour < 6 || hour >= 18) ? 'dark' : 'light';
}
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
async function updateTheme(theme) {
const root = document.documentElement;
const newTheme = theme === 'dark' ? 'dark' : 'light';
const isDark = newTheme === 'dark';
try {
const config = await getUci();
const primaryRgbbody = isDark ? '33,45,60' : '248,248,248';
const bgqsValue = config.bgqs || "0";
const rgbmValue = config.primaryrgbm || '45,102,147';
const rgbmtsValue = config.primaryrgbmts || '0';
const meta = document.querySelector('meta[name="theme-color"]');
if (meta) {
meta.content = isDark ? '#1a1a1a' : '#ffffff';
}
const vars = bgqsValue === "0" ? {
'--menu-fontcolor': isDark ? '#ddd' : '#f5f5f5',
'--primary-rgbbody': primaryRgbbody,
'--bgqs-image': '-webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.1) 75%, transparent 75%, transparent)',
'--menu-bgcolor': `rgba(${rgbmValue}, ${rgbmtsValue})`,
'--menu-item-hover-bgcolor': 'rgba(248,248,248, 0.22)',
'--menu-item-active-bgcolor': 'rgba(248,248,248, 0.3)',
} : {
'--menu-fontcolor': isDark ? '#ddd' : '#4d4d5d',
'--primary-rgbbody': primaryRgbbody,
'--menu-bgcolor': `rgba(${primaryRgbbody},${rgbmtsValue})`,
};
Object.entries(vars).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
if (window.LuciForm) LuciForm.refreshVisibility();
} catch (error) {
console.error('Error updating theme variables:', error);
}
}
(async function(){
const config = await getUci();
var initMode = config.mode;
var autoTheme;
function applyTheme(theme) {
document.body.setAttribute('data-theme', theme);
const meta = document.querySelector('meta[name="theme-color"]');
if (meta) {
meta.content = theme === 'dark' ? '#1a1a1a' : '#ffffff';
}
}
(async function() {
if (initMode === 'auto') {
autoTheme = getTimeTheme();
} else {
autoTheme = initMode;
}
applyTheme(autoTheme);
await updateTheme(autoTheme);
})();
})();
/* ]]> */

View File

@@ -0,0 +1,60 @@
/*
* luci-theme-kucat
* Copyright (C) 2019-2024 The Sirpdboy Team <herboy2008@gmail.com>
*
* Have a bug? Please create an issue here on GitHub!
* https://github.com/sirpdboy/luci-theme-kucat/issues
*
* Licensed to the public under the Apache License 2.0
*/
function pdopenbar() {
var leftBar = document.getElementById("header-bar-left");
var rightBar = document.getElementById("header-bar-right");
leftBar.style.cssText = "width:300px;display:block !important";
rightBar.style.cssText = "width:0;display:none !important";
}
function pdclosebar() {
var leftBar = document.getElementById("header-bar-left");
var rightBar = document.getElementById("header-bar-right");
leftBar.style.cssText = "width:0;display:none !important";
rightBar.style.cssText = "width:50px;display:block !important";
}
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'ArrowLeft') pdopenbar();
if (e.ctrlKey && e.key === 'ArrowRight') pdclosebar();
});
});
function initScrollContainers() {
document.querySelectorAll('.cbi-section, .mainmenu').forEach(section => {
const content = section.querySelector('.content');
if (!content) return;
const checkOverflow = () => {
section.classList.toggle(
'auto-scroll-container',
content.scrollHeight > section.clientHeight
);
};
checkOverflow();
new MutationObserver(checkOverflow).observe(content, { childList: true, subtree: true });
section.addEventListener('touchstart', () => {
section.classList.add('touch-active');
}, { passive: true });
section.addEventListener('touchend', () => {
setTimeout(() => section.classList.remove('touch-active'), 1000);
}, { passive: true });
});
}
document.addEventListener('DOMContentLoaded', initScrollContainers);

View File

@@ -0,0 +1,120 @@
/* <![CDATA[ */
function syncToUci(theme) {
fetch('/cgi-bin/luci/api/set', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'theme=' + encodeURIComponent(theme)
}).catch(console.error);
}
async function syncgetUci() {
try {
const response = await fetch("/cgi-bin/luci/api/get");
if (!response.ok) throw new Error("Network error");
return await response.json();
} catch (error) {
console.error("Fetch config failed, using default:", error);
return {
success: false,
bgqs: "1",
primaryrgbm: "45,102,147",
primaryrgbmts: "0",
mode: "light"
};
}
}
// Theme Detection
function getTimeBasedTheme() {
const hour = new Date().getHours();
// console.debug('hour:', hour);
return (hour < 6 || hour >= 18) ? 'dark' : 'light';
}
// Theme Application
async function updateThemeVariables(theme) {
const root = document.documentElement;
const isDark = theme === 'dark';
try {
const config = await syncgetUci();
const primaryRgbbody = isDark ? '33,45,60' : '248,248,248';
const bgqsValue = config.bgqs || "1";
const rgbmValue = config.primaryrgbm || '45,102,147';
const rgbmtsValue = config.primaryrgbmts || '0';
const vars = bgqsValue === "0" ? {
'--menu-fontcolor': isDark ? '#ddd' : '#f5f5f5',
'--primary-rgbbody': primaryRgbbody,
'--bgqs-image': '-webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.1) 75%, transparent 75%, transparent)',
'--menu-bgcolor': `rgba(${rgbmValue}, ${rgbmtsValue})`,
'--menu-item-hover-bgcolor': 'rgba(248,248,248, 0.22)',
'--menu-item-active-bgcolor': 'rgba(248,248,248, 0.3)',
} : {
'--menu-fontcolor': isDark ? '#ddd' : '#4d4d5d',
'--primary-rgbbody': primaryRgbbody,
'--menu-bgcolor': `rgba(${primaryRgbbody},${rgbmtsValue})`,
};
Object.entries(vars).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
if (window.LuciForm) {
LuciForm.refreshVisibility();
}
} catch (error) {
console.error('Error updating theme variables:', error);
}
}
document.getElementById('themeToggle').addEventListener('click', function() {
const switcher = this;
const isDark = switcher.dataset.theme === 'dark';
const newTheme = isDark ? 'light' : 'dark';
switcher.dataset.theme = newTheme;
document.querySelectorAll('.theme-switcher span').forEach(span => {
span.classList.toggle('active');
});
document.body.setAttribute('data-theme', newTheme);
// console.debug('switcher:', switcher.dataset.theme,newTheme);
syncToUci(newTheme);
updateThemeVariables(newTheme);
});
window.addEventListener('DOMContentLoaded', async function() {
const config = await syncgetUci();
function applyTheme(theme) {
document.body.setAttribute('data-theme', theme);
const meta = document.querySelector('meta[name="theme-color"]');
const switcher = document.getElementById('themeToggle');
switcher.dataset.theme = theme;
if (theme === 'dark') {
switcher.querySelector('.pdboy-dark').classList.add('active');
switcher.querySelector('.pdboy-light').classList.remove('active');
} else {
switcher.querySelector('.pdboy-light').classList.add('active');
switcher.querySelector('.pdboy-dark').classList.remove('active');
}
if (meta) {
meta.content = theme === 'dark' ? '#1a1a1a' : '#ffffff';
}
}
const themeToApply = config.mode === 'auto'
? getTimeBasedTheme()
: (config.mode || 'light');
// console.debug('switcher:', config.mode,themeToApply);
applyTheme(themeToApply);
await updateThemeVariables(themeToApply);
});
/* ]]> */

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1,50 @@
{
"name": "EzOpWRT Admin",
"short_name": "LuCI",
"description":"Kucat for OpenWRT by sirpdboy.",
"start_url": ".",
"scope": "/",
"orientation":"any",
"splash_pages": null,
"background_color": "#f8f8f8",
"theme_color": "#2d3a4b",
"theme_color_light": "#ffffff",
"theme_color_dark": "#1a1a1a",
"prompt_message":"fast read Kucat",
"icons": [
{
"src": "/luci-static/kucat/logo.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/luci-static/kucat/logo.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/luci-static/kucat/img/logo70.png",
"sizes": "70x70",
"type": "image/png"
},
{
"src": "/luci-static/kucat/img/logo150.png",
"sizes": "150x150",
"type": "image/png"
},
{
"src": "/luci-static/kucat/img/logo310.png",
"sizes": "310x310",
"type": "image/png"
},
{
"src": "/luci-static/kucat/img/logo512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"display":"standalone"
}

View File

@@ -0,0 +1,217 @@
/*
* luci-theme-kucat
* Copyright (C) 2019-2024 The Sirpdboy Team <herboy2008@gmail.com>
*
* Have a bug? Please create an issue here on GitHub!
* https://github.com/sirpdboy/luci-theme-kucat/issues
*
* luci-theme-bootstrap:
* Copyright 2008 Steven Barth <steven@midlink.org>
* Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
* Copyright 2012 David Menting <david@nut-bolt.nl>
*
* luci-theme-material:
* https://github.com/LuttyYang/luci-theme-material/
* luci-theme-opentopd:
* https://github.com/sirpdboy/luci-theme-opentopd
*
* Licensed to the public under the Apache License 2.0
*/
'use strict';
'require baseclass';
'require ui';
return baseclass.extend({
__init__: function() {
ui.menu.load().then(L.bind(this.render, this));
},
render: function(tree) {
var node = tree,
url = '';
this.renderModeMenu(node);
if (L.env.dispatchpath.length >= 3) {
for (var i = 0; i < 3 && node; i++) {
node = node.children[L.env.dispatchpath[i]];
url = url + (url ? '/' : '') + L.env.dispatchpath[i];
}
if (node)
this.renderTabMenu(node, url);
}
document.querySelector('.showSide')
.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
document.querySelector('.darkMask')
.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
document.querySelector(".main > .loading").style.opacity = '0';
document.querySelector(".main > .loading").style.visibility = 'hidden';
if (window.innerWidth <= 1152)
document.querySelector('.main-left').style.width = '0';
document.querySelector('.main-right').style.overflow = 'auto';
window.addEventListener('resize', this.handleSidebarToggle, true);
},
handleMenuExpand: function(ev) {
var a = ev.target, ul1 = a.parentNode, ul2 = a.nextElementSibling;
var collapse = false;
document.querySelectorAll('li.slide.active').forEach(function(li) {
if (li !== a.parentNode || li == ul1) {
li.classList.remove('active');
li.childNodes[0].classList.remove('active');
}
if (!collapse && li == ul1) {
collapse = true;
}
});
if (!ul2)
return;
if (ul2.parentNode.offsetLeft + ul2.offsetWidth <= ul1.offsetLeft + ul1.offsetWidth)
ul2.classList.add('align-left');
if (!collapse) {
ul1.classList.add('active');
a.classList.add('active');
}
else
{
ul1.classList.remove('active');
a.classList.remove('active');
}
a.blur();
ev.preventDefault();
ev.stopPropagation();
},
renderMainMenu: function(tree, url, level) {
var l = (level || 0) + 1,
ul = E('ul', { 'class': level ? 'slide-menu' : 'nav' }),
children = ui.menu.getChildren(tree);
if (children.length == 0 || l > 2)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = ((L.env.dispatchpath[l] == children[i].name) && (L.env.dispatchpath[l - 1] == tree.name)),
submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, l),
hasChildren = submenu.children.length,
slideClass = hasChildren ? 'slide' : null,
menuClass = hasChildren ? 'menu' : null;
if (isActive) {
ul.classList.add('active');
slideClass += " active";
menuClass += " active";
}
ul.appendChild(E('li', { 'class': slideClass }, [
E('a', {
'href': L.url(url, children[i].name),
'click': (l == 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null,
'class': menuClass,
'data-title': hasChildren ? children[i].title.replace(" ", "_") : children[i].title.replace(" ", "_"),
}, [_(children[i].title)]),
submenu
]));
}
if (l == 1) {
var container = document.querySelector('#mainmenu');
container.appendChild(ul);
container.style.display = '';
}
return ul;
},
renderModeMenu: function(tree) {
var ul = document.querySelector('#modemenu'),
children = ui.menu.getChildren(tree);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
ul.appendChild(E('li', {}, [
E('a', {
'href': L.url(children[i].name),
'class': isActive ? 'active' : null
}, [ _(children[i].title) ])
]));
if (isActive)
this.renderMainMenu(children[i], children[i].name);
if (i > 0 && i < children.length)
ul.appendChild(E('li', {'class': 'divider'}, [E('span')]))
}
if (children.length > 1)
ul.parentElement.style.display = '';
},
renderTabMenu: function(tree, url, level) {
var container = document.querySelector('#tabmenu'),
l = (level || 0) + 1,
ul = E('ul', { 'class': 'tabs' }),
children = ui.menu.getChildren(tree),
activeNode = null;
if (children.length == 0)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l + 2] == children[i].name),
activeClass = isActive ? ' active' : '',
className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);
ul.appendChild(E('li', { 'class': className }, [
E('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )
]));
if (isActive)
activeNode = children[i];
}
container.appendChild(ul);
container.style.display = '';
if (activeNode)
container.appendChild(this.renderTabMenu(activeNode, url + '/' + activeNode.name, l));
return ul;
},
handleSidebarToggle: function(ev) {
var width = window.innerWidth,
darkMask = document.querySelector('.darkMask'),
mainRight = document.querySelector('.main-right'),
mainLeft = document.querySelector('.main-left'),
open = mainLeft.style.width == '';
if (width > 1152 || ev.type == 'resize')
open = true;
darkMask.style.visibility = open ? '' : 'visible';
darkMask.style.opacity = open ? '': 1;
if (width <= 1152)
mainLeft.style.width = open ? '0' : '';
else
mainLeft.style.width = ''
mainLeft.style.visibility = open ? '' : 'visible';
mainRight.style['overflow-y'] = open ? 'visible' : 'hidden';
}
});

View File

@@ -0,0 +1,74 @@
module("luci.controller.api", package.seeall)
http = require "luci.http"
fs = require "nixio.fs"
uci = require "luci.model.uci".cursor()
json = require "luci.jsonc"
function index()
entry({"api", "get"}, call("get_theme"), nil, 10)
entry({"api", "set"}, call("set_theme"), nil, 20)
end
function get_theme()
local kucat = nil
local config_exists = false
local bgqs = "1"
local primaryrgbm = "45,102,147"
local primaryrgbmts = "0"
local mode = "auto"
if fs.access("/etc/config/advancedplus") then
kucat = "advancedplus"
config_exists = true
elseif fs.access("/etc/config/kucat") then
kucat = "kucat"
config_exists = true
end
if config_exists then
local ku = uci:get_all(kucat, "@basic[0]") or {}
bgqs = ku.bgqs or bgqs
primaryrgbm = ku.primary_rgbm or primaryrgbm
primaryrgbmts = ku.primary_rgbm_ts or primaryrgbmts
mode = ku.mode or mode
end
http.prepare_content("application/json")
http.write_json({
success = config_exists,
bgqs = bgqs,
primaryrgbm = primaryrgbm,
primaryrgbmts = primaryrgbmts,
mode = mode
})
end
function set_theme()
local kucat = nil
local config_exists = false
local theme = http.formvalue("theme")
if fs.access("/etc/config/advancedplus") then
kucat = 'advancedplus'
config_exists = true
elseif fs.access("/etc/config/kucat") then
kucat = 'kucat'
config_exists = true
end
if (config_exists) then
local esc_kucat = "'" .. kucat:gsub("'", "'\\''") .. "'"
local esc_theme = "'" .. theme:gsub("'", "'\\''") .. "'"
os.execute(string.format(
"uci set %s.@basic[0].mode=%s && uci commit %s",
kucat, esc_theme, kucat
))
uci:set(kucat, "@basic[0]", "mode", theme)
uci:commit(kucat)
http.prepare_content("application/json")
http.write_json({ success = true })
else
http.prepare_content("application/json")
http.write_json({ success = false })
end
end

View File

@@ -0,0 +1,25 @@
<%#
Copyright (C) 2019-2025 The Sirpdboy Team <herboy2008@gmail.com>
luci-theme-material:
Copyright 2015 Lutty Yang <lutty@wcan.in>
Licensed to the public under the Apache License 2.0
-%>
<% local ver = require "luci.version" %>
</div>
<footer>
<div class="mobile-hide">
<a class="luci-link" href="https://github.com/openwrt/luci" target="_blank">Powered by <%= ver.luciname %> /
<a href="https://github.com/sirpdboy/kucat" target="_blank">KuCatTheme <%# vPKG_VERSION %></a>
</div>
</footer>
<div class="footend">
</div>
</div>
</div>
<script src="<%=media%>/js/footend.js"></script>
<script type="text/javascript">L.require('menu-kucat')</script>
</body>
</html>

View File

@@ -0,0 +1,346 @@
<%#
Copyright (C) 2019-2025The Sirpdboy Team <herboy2008@gmail.com>
luci-theme-material:
Copyright 2015 Lutty Yang <lutty@wcan.in>
luci-theme-bootstrap:
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
Copyright 2012 David Menting <david@nut-bolt.nl>
MUI:
https://github.com/muicss/mui
Licensed to the public under the Apache License 2.0
-%>
<%
local sys = require "luci.sys"
local util = require "luci.util"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local ver = require "luci.version"
local json = require "luci.jsonc"
local fs = require "nixio.fs"
local nutil = require "nixio.util"
local uci = require 'luci.model.uci'.cursor()
local urlencode = luci.http.protocol and luci.http.protocol.urlencode or luci.util.urlencode
local boardinfo = util.ubus("system", "board") or {}
local node = disp.context.dispatched
local path = table.concat(disp.context.path, "-")
local bar = '/cgi-bin/luci/admin/'
local config_exists = false
local kucat = ''
function glob(...)
local iter, code, msg = fs.glob(...)
if iter then
return nutil.consume(iter)
end
return nil, code, msg
end
function getExtension(str)
return urlencode(str:match(".+%.(%w+)$") or "")
end
http.prepare_content("text/html; charset=UTF-8")
if fs.access("/etc/config/advancedplus") then
kucat = "advancedplus"
config_exists = true
elseif fs.access("/etc/config/kucat") then
kucat = "kucat"
config_exists = true
end
local config = {}
local primary_rgbm, primary_rgbm_ts, primary_opacity, primary_rgbs, primary_rgbs_ts, mode
local gohome, gouser, gossr, bgqs, setbar, dayword
local background, bkuse, bklock, fontd, fontz, fontx
if config_exists then
config = uci:get_all(kucat, "@basic[0]") or {}
primary_rgbm = config.primary_rgbm or '45,102,147'
primary_rgbm_ts = config.primary_rgbm_ts or '0.8'
primary_opacity = config.primary_opacity or '0'
primary_rgbs = config.primary_rgbs or '132,188,218'
primary_rgbs_ts = config.primary_rgbs_ts or '0.1'
mode = config.mode or 'light'
gohome = bar .. "status/" .. (config.gohome or "")
gouser = bar .. "system/" .. (config.gouser or "")
gossr = bar .. "services/" .. (config.gossr or "")
bgqs = config.bgqs or '0'
setbar = config.setbar or '1'
dayword = config.dayword or '0'
background = config.background or '0'
bkuse = config.bkuse or '0'
bklock = config.bklock or '1'
fontd = config.font_d or '1.2rem'
fontz = config.font_z or '0.92rem'
fontx = config.font_x or '0.875rem'
else
primary_rgbm = '45,102,147'
primary_rgbm_ts = '0.8'
primary_opacity = '0'
primary_rgbs = '132,188,218'
primary_rgbs_ts = '0.1'
mode = 'light'
gohome = bar .. "status/"
gouser = bar .. "system/"
gossr = bar .. "services/"
bgqs = '1'
setbar = '1'
dayword = '0'
background = '0'
bkuse = '0'
bklock = '1'
fontd = '1.2rem'
fontz = '0.92rem'
fontx = '0.875rem'
end
local bk_use = 'background: rgba(var(--primary-rgbbody),1)'
local loginbk_use = 'background: linear-gradient(0deg, rgba(var(--primary-rgbm), 1) 0%, rgba(var(--primary-rgbbody), 1) 100%);display: block;'
local ufilter = string.format("blur(%dpx)", tonumber(primary_opacity))
if primary_opacity == '0' then
ufilter = 'none'
end
local bg_url = media..'/img/bg1.jpg'
local bg_lock = bg_url
if background == '0' then
math.randomseed(os.time())
local theme_dir = resource .. '/background/'
if not fs.access('/www' .. resource .. '/background/') then
local theme_dir = media .. '/background/'
end
local bgcount = 0
local currentBg = {}
local bgs,attr = {}
for i, f in ipairs(glob("/www" .. theme_dir .. "*")) do
attr = fs.stat(f)
if attr then
local ext = getExtension(fs.basename(f))
if ext == "jfif" or ext == "pjp" or ext == "pjpeg" or ext == "jpeg" or ext == "jpg" or ext == "png" or ext == "gif" then
local bg = {}
bg.type = ext
bg.url = theme_dir .. fs.basename(f)
table.insert(bgs,bg)
bgcount = bgcount + 1
end
end
end
if bgcount > 0 then
currentBg = bgs[math.random(1,bgcount)]
picurl = currentBg.url
end
else
picurl = sys.exec("/usr/libexec/kucat/kucat_wallpaper")
end
if (picurl and picurl ~= '') then
bg_url = picurl
end
if bklock == '0' then
math.randomseed(os.time())
local bk_dir = media .. "/bg/"
local bkcount = 0
local currentbk = {}
local bgs,attr = {}
for i, f in ipairs(glob("/www" .. bk_dir .. "*")) do
attr = fs.stat(f)
if attr then
local ext = getExtension(fs.basename(f))
if ext == "jfif" or ext == "pjp" or ext == "pjpeg" or ext == "jpeg" or ext == "jpg" or ext == "png" or ext == "gif" then
local bg = {}
bg.type = ext
bg.url = bk_dir .. fs.basename(f)
table.insert(bgs,bg)
bkcount = bkcount + 1
end
end
end
if bkcount > 0 then
currentbk = bgs[math.random(1,bkcount)]
bg_lock = currentbk.url
end
end
if dayword == '1' then
showword = sys.exec("/usr/libexec/kucat/kucat_word")
end
if bkuse == '1' then
if bklock == '0' then
bk_use = "background-image:url(" .. bg_lock .. ")"
loginbk_use = "background-image:url(" .. bg_url .. ")"
else
bk_use="background-image:url("..bg_url..")"
loginbk_use = "background-image:url(" .. bg_url .. ")"
end
else
if bklock == '0' then
loginbk_use = "background-image:url(" .. bg_url .. ")"
end
end
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.5, viewport-fit=cover">
<meta name="theme-color" content="#ffffff">
<meta name="color-scheme" content="light dark">
<meta name="msapplication-TileColor" content="<%=(mode == 'dark' and '#1a1a1a' or '#ffffff')%>">
<link rel="preload" href="<%=media%>/css/style.css?v=<%= ver.luciversion %>" as="style">
<link rel="preload" href="<%=media%>/css/theme.css?v=<%= ver.luciversion %>" as="style">
<meta name="x5-fullscreen" content="true">
<meta name="full-screen" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="format-detection" content="telephone=no, email=no, address=no">
<link rel="manifest" href="<%=media%>/manifest.json" crossorigin="use-credentials">
<link rel="shortcut icon" href="<%=media%>/favicon.ico" type="image/x-icon">
<link rel="icon" href="<%=media%>/logo.png" type="image/png">
<link rel="mask-icon" href="<%=media%>/logo.png" color="#ffffff">
<link rel="apple-touch-icon" href="<%=media%>/img/logo180.png">
<link rel="apple-touch-icon" sizes="150x150" href="<%=media%>/img/logo150.png">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="msapplication-config" content="<%=media%>/browserconfig.xml">
<meta name="msapplication-TileImage" content="<%=media%>/logo.png">
<title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
<link rel="stylesheet" href="<%=media%>/css/style.css?v=<%= ver.luciversion %>" media="print" onload="this.media='all'">
<link rel="stylesheet" href="<%=media%>/css/theme.css?v=<%= ver.luciversion %>" media="print" onload="this.media='all'">
<noscript>
<link rel="stylesheet" href="<%=media%>/css/style.css?v=<%= ver.luciversion %>">
<link rel="stylesheet" href="<%=media%>/css/theme.css?v=<%= ver.luciversion %>">
</noscript>
</head>
<% if node and node.css then %>
<link rel="stylesheet" href="<%=resource%>/<%=node.css%>">
<% end -%>
<style title="text/css">
<% if css then %>
<%= css %>
<% end %>
:root {
<% if mode == 'dark' then %>
--primary-rgbbody: 33,45,60;--menu-hover-color: #f5f5f5f5;--menu-fontcolor: #ddd;--primarytextcolor: #bbb;--primary-title-color: #ccc;--menu-color: #ddd;--title-color: #ddd;--body-color: #bbb;--menu-item-titlebg-color: rgba(var(--primary-rgbm),0.5);--body-hover-bgcolor: rgba(255,255,255,0.05);
<% else %>
--menu-item-titlebg-color: rgba(var(--primary-rgbm),0.22);--body-hover-bgcolor: rgba(50,50,50,0.05);--primary-rgbbody: 248,248,248; --menu-hover-color: #fff;--menu-fontcolor: #f5f5f5f5;--primarytextcolor: #677087;--primary-title-color: #4d4d5d;--menu-color: #eee; --title-color: #65656b; --body-color: #65656b;
<% end -%>
--primary-rgbm: <%=primary_rgbm%>;--primary-rgbm-ts: <%=primary_rgbm_ts%>;--primary-rgbs: <%=primary_rgbs%>;--primary-rgbs-ts: <%=primary_rgbs_ts%>;--font-d: <%=fontd%>;--font-z: <%=fontz%>;--font-x: <%=fontx%>; --ufilter: <%=ufilter%>;
<%if bgqs == '0' then %>
--bgqs-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.1) 75%, transparent 75%, transparent);
--menu-bgcolor: rgba(var(--primary-rgbm), var(--primary-rgbm-ts)); --menu-item-hover-bgcolor: rgba(248,248,248, 0.22); --menu-item-active-bgcolor: rgba(248,248,248, 0.3);
<% else %>
--bgqs-image: none; --menu-bgcolor: rgba(var(--primary-rgbbody), var(--primary-rgbm-ts));--menu-item-hover-bgcolor: rgba(var(--primary-rgbm), 0.8); --menu-item-active-bgcolor: rgba(var(--primary-rgbm), var(--primary-rgbm-ts));
<% if mode == 'dark' then %>
--menu-fontcolor: #ddd;
<% else %>
--menu-fontcolor: #4d4d5d;
<% end -%>
}
@media screen and (max-width: 1152px) { .main .main-left {background-color:rgba(var(--primary-rgbbody), 1);}}
<% end -%>
<% if setbar == "1" then %>
header.bar-primary .container-bar-left {width: 300px;display: block;}
header.bar-primary .container-bar-right { width: 0; display: none; }
<% else %>
header.bar-primary .container-bar-left { width: 0;display: none;}
header.bar-primary .container-bar-right {width: 50px;display: block;}
<% end -%>
</style>
<script src="<%=url('admin/translations', luci.i18n.context.lang)%>?v=<%=ver.luciversion%>"></script>
<script src="<%=resource%>/cbi.js?v=<%=ver.luciversion%>"></script>
<script src="<%=resource%>/luci.js?v=<%=ver.luciversion%>"></script>
<script src="<%=media%>/js/style.js"></script>
</head>
<body class="lang_<%=luci.i18n.context.lang%> <% if luci.dispatcher.context.authsession then %>logged-in<% end %> <% if not (path == "") then %>node-<%= path %><% else %>node-main-login<% end %>" data-page="<%= pcdata(path) %>" style="<%=bk_use%>; "
<% if mode ~= 'auto' then %>
data-theme = "<%= mode %>"
<% end -%>
>
<header class="bar-primary">
<div id="header-bar-left" class="container-bar-left">
<a class="labelbar pdboy-closebar" href="javascript:void(0)" onclick="pdclosebar()" title="Close" ></a>
<a class="labelbar pdboy-gohome" href="<%=gohome%>" title="<%:Status%>"></a>
<a class="labelbar pdboy-goadvanced" href="<%=gouser%>" title="<%:System%>"></a>
<a class="labelbar pdboy-gossr" href="<%=gossr%>" title="<%:Services%>"></a>
<a class="labelbar pdboy-gonet" href="<%=url("admin/network/network")%>" title="<%:Network%>"></a>
<a class="labelbar pdboy-gopoweroff" href="<%=url("admin/system/reboot")%>" title="<%:Reboot%>"></a>
</div>
<div id="header-bar-right" class="container-bar-right">
<a class="labelbar pdboy-openbar" href="javascript:void(0)" title="Open" onclick="pdopenbar()"></a>
</div>
</header>
<div class="main">
<div style="" class="loading">
</div>
<div class="main-left">
<div class="sidenav-header d-flex align-items-center">
<a class="brand" href="/"><%=boardinfo.hostname or "?"%></a>
</div>
<div class="mainmenu" id="mainmenu" style="display:none"></div>
</div>
<div class="main-right">
<header class="pd-primary">
<div class="fill">
<div class="container">
<div class="flex1">
<a class="showSide" title="<%:Menu%>" ></a>
<a class="brand" href="/"><%=striptags(boardinfo.hostname or "OpenWrt")%></a>
<% if dayword == '1' then %>
<a class="showWord" href="#"><%=showword%></a>
<% end -%>
</div>
<div class="status" id="indicators"></div>
<div class="theme-switcher" id="themeToggle" data-theme="<%= mode %>">
<span class="pdboy-light active"></span>
<span class="pdboy-dark"></span>
</div>
<script src="<%=media%>/js/switcher.js"></script>
<span > <a class="pdboy-qlogout" href="<%=url("admin/logout")%>" title="<%:Log_out%>" > </a></span>
</div>
</div>
</header>
<div class="modemenu-buttons" style="display:none">
<ul id="modemenu"></ul>
</div>
<div class="darkMask"></div>
<div class="login-bg" style="<%=loginbk_use%>"></div>
<div id="maincontent">
<div class="container">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") then -%>
<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 disp.lookup("admin/system/admin") then %>
<div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
<% end %>
</div>
<%- end -%>
<noscript>
<div class="alert-message warning">
<h4><%:JavaScript required!%></h4>
<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>
</div>
</noscript>
<div id="tabmenu" style="display:none"></div>

View File

@@ -0,0 +1,13 @@
#!/bin/sh
if [ "$PKG_UPGRADE" != 1 ]; then
uci get luci.themes.KuCat >/dev/null 2>&1 || \
uci batch <<-EOF
set luci.themes.KuCat=/luci-static/kucat
set luci.main.mediaurlbase=/luci-static/kucat
commit luci
EOF
fi
chmod +x /usr/libexec/kucat/kucat*
rm -rf /tmp/luci-*
exit 0

View File

@@ -0,0 +1,99 @@
#!/bin/sh
# luci-theme-kucat
# Copyright (C) 2019-2025 The Sirpdboy Team <herboy2008@gmail.com>
#
# Have a bug? Please create an issue here on GitHub!
# https://github.com/sirpdboy/luci-theme-kucat/issues
#
# Licensed to the public under the Apache License 2.0
# author github@sirpdboy
kucat='kucat'
[ -s "/etc/config/advancedplus" ] &&kucat='advancedplus'
background="$(uci -q get $kucat.@basic[0].background || echo '0')"
KUCATTMP="/var/kucat_date_${background}.tmp"
BGURL="/www/luci-static/kucat/img/down${background}.jpg"
REBGURL="/luci-static/kucat/img/down${background}.jpg"
DFBGURL="/luci-static/kucat/img/bg1.jpg"
WRLOCK="/var/lock/kucat_lock_${background}.lock"
test_url() {
url=$1
local status=$(/usr/bin/curl -I -o /dev/null -skL --connect-timeout 1 --retry 1 -w %{http_code} "$url")
case "$status" in
204|\
200)
echo '1'
;;
esac
}
curl_bgurl() {
case "$background" in
1)
local ppath=`curl -s 'https://open.iciba.com/dsapi/' | jsonfilter -qe '@.picture4'`
[ -n "${ppath}" ] && echo "$ppath"
;;
2)
curl -fks --max-time 3 \
--header "Authorization: Client-ID kmFIroj2ELqXJPtC0XUoyww-Tr_lDU8Ho8uxjptIrCo" \
"https://api.unsplash.com/photos/random?count=1&orientation=landscape" |
jsonfilter -e "@[0]['urls']['regular']"
;;
3)
local ppath=`curl -s "https://www.bing.com/HPImageArchive.aspx?format=js&n=1" |jsonfilter -qe '@.images[0].url'`
[ -n "${ppath}" ] && echo "https://www.bing.com${ppath}"
;;
4)
local i=`awk 'BEGIN{srand();print int(rand()*8)}'`
local j=`awk 'BEGIN{srand();print int(rand()*200)}'`
local ppath=`curl -s "http://wp.birdpaper.com.cn/intf/search?content=4k&pageno=$j&count=9" | awk -F '\"count\":9' '{print $2}' | awk -F ',\"processTime\"' '{print $1}' | sed 's#,#{#' | jsonfilter -e "@.list[$i].url"`
[ -n "${ppath}" ] && echo "$ppath"
;;
5)
curl -fks --max-time 3 \
"https://wallhaven.cc/api/v1/search?resolutions=1920x1080&sorting=random" |
jsonfilter -qe '@.data[0].path'
;;
esac
}
bgurl_down() {
local lock="$WRLOCK"
exec 200>$lock
if flock -n 200 >/dev/null 2>&1; then
local bgurl="$(curl_bgurl)"
if [ -n "$bgurl" ]; then
rm -rf $BGURL
curl -kLfsm 3 $bgurl -o $BGURL
date +%Y%m%d > $KUCATTMP
fi
flock -u 200 >/dev/null 2>&1
fi
[ -s "$BGURL" ] && echo -ne $REBGURL || echo -ne $DFBGURL
}
check_url() {
if [ -s $KUCATTMP ]; then
localtime=`cat $KUCATTMP | grep $(date +%Y%m%d) `
if [ $localtime ]; then
if [ -s $BGURL ] ; then
echo -ne $REBGURL
return
fi
fi
fi
# local checknet=$(test_url "https://www.baidu.com")
checknet=`ping -c 1 -W 1 223.5.5.5 > /dev/null 2>&1 &&echo 1 || echo 2`
if [ "x$checknet" == "x1" ]; then
bgurl_down
else
[ -s "$DFBGURL" ] && echo -ne $DFBGURL
fi
}
check_url

View File

@@ -0,0 +1,96 @@
#!/bin/sh
# luci-theme-kucat
# Copyright (C) 2019-2024 The Sirpdboy Team <herboy2008@gmail.com>
#
# Have a bug? Please create an issue here on GitHub!
# https://github.com/sirpdboy/luci-theme-kucat/issues
#
# Licensed to the public under the Apache License 2.0
LANG=`uci get luci.main.lang`
WORDDATETMP="/var/dayword_kucatword_date.tmp"
LOCK="/var/lock/dayword_kucatword.lock"
WORDTMP="/var/dayword_kucatword_$LANG.tmp"
test_url() {
url=$1
status="$(/usr/bin/curl -I -o /dev/null -skL --connect-timeout 1 --retry 1 -w %{http_code} "$url")"
case "$status" in
204|\
200)
echo '1'
;;
esac
}
get_word() {
kucat='kucat'
[ -s "/etc/config/advancedplus" ] &&kucat='advancedplus'
dayword="$(uci -q get $kucat.@basic[0].dayword || echo '0')"
case "$dayword" in
1)
[ "X$LANG" == "Xen" ] && gwordjson=`curl -s 'https://open.iciba.com/dsapi/' | jsonfilter -qe '@.content'` || gwordjson=`curl -s 'https://open.iciba.com/dsapi/' | jsonfilter -qe '@.note'`
[ -n "${gwordjson}" ] && echo "$gwordjson"
;;
2)
# form iciba : https://api.vvhan.com/api/en
[ "X$LANG" == "Xen" ] && gwordjson=`curl -s 'https://api.vvhan.com/api/en?type=sj'| jsonfilter -qe '@.data.zh'` || gwordjson=`curl -s 'https://api.vvhan.com/api/en?type=sj'| jsonfilter -qe '@.data.en'`
[ -n "${gwordjson}" ] && echo "$gwordjson"
;;
3)
local gwordjson=`curl -s 'https://api.yixiangzhilv.com/yiyan/sentence/get/'`
local gword=`echo $gwordjson | jsonfilter -qe '@.content'`
local gfrom=`echo $gwordjson | jsonfilter -qe '@.author'`
[ -n "${gfrom}" ] && gfrom=$(echo " ---- $gfrom")|| gfrom=''
[ -n "${gword}" ] && echo "$gword $gfrom"
;;
4)
local gwordjson=`curl -s 'https://yijuzhan.com/api/word.php?m=json'`
local gword=`echo $gwordjson | jsonfilter -qe '@.content'`
local gfrom=`echo $gwordjson | jsonfilter -qe '@.source'`
[ -n "${gfrom}" ] && gfrom=$(echo " ---- $gfrom")|| gfrom=''
[ -n "${gword}" ] && echo "$gword $gfrom"
;;
5)
local gword=`curl -s https://v.api.aa1.cn/api/api-wenan-dujitang/index.php?aa1=json | sed 's/\[//g' | sed 's/\]//g'| jsonfilter -qe '@.dujitang'`
[ -n "${gword}" ] && echo "$gword"
;;
*)
local gwordjson=`curl -s 'https://v1.hitokoto.cn'`
local gword=`echo $gwordjson | jsonfilter -qe '@.hitokoto'`
local gfrom=`echo $gwordjson | jsonfilter -qe '@.from_who'`
[ -n "${gfrom}" ] && gfrom=$(echo " ---- $gfrom")|| gfrom=''
[ -n "${gword}" ] && echo "$gword $gfrom"
;;
esac
}
kucatword() {
lock="$LOCK"
exec 200>"$lock"
if flock -n 200 >"/dev/null" 2>&1; then
local localword="$(get_word)"
if [ -n "$localword" ]; then
echo "$localword" >$WORDTMP
date +%Y%m%d > $WORDDATETMP
fi
flock -u 200 >"/dev/null" 2>&1
fi
[ -s "$WORDTMP" ] && cat $WORDTMP || echo -ne "Acquisition failed ,Please Issues:github@sirpdboy"
}
check_url() {
if [ -f "$WORDDATETMP" ]; then
localtime=`cat $WORDDATETMP | grep $(date +%Y%m%d) `
if [ $localtime ]; then
[ -s "$WORDTMP" ] && cat $WORDTMP && return
fi
fi
local checknet=`ping -c 1 -W 1 223.5.5.5 > /dev/null 2>&1 &&echo 1 || echo 2`
if [ "x$checknet" == "x1" ]; then
kucatword
else
echo -ne "No internet connection, no information."
fi
}
check_url