v46.1-ui-mt76: Argon theme + hardened WG tunnel + Link Health dashboard
基于 v46 正式版(mt76)只恢复 UI 层: - luci-theme-argon 2.4.3: local-background-wins 登录页 + bg1.jpg fallback - luci-app-argon-config: 去 ui.changes.apply, ACL mutator 移 write - luci-app-wgtunnel: rpcd ucode 后端(get/status/prepare/apply/rollback/reconnect), JSONMap 前端, 60s 一次性 token, 快照回滚, 无全局 network ACL - luci-app-tr3000-status: 只读 rpcd ucode + 5s 轮询仪表盘 - tools/: audit_ui_packages.py + install_preview.sh + rollback_watchdog.sh - docs/: 开发经历与翻车记录 + 固件哈希记录 固件本体(含烤入 WG 私钥/PSK)不入 git, 仅 K 盘保存. kernel 成员与 v46 byte-identical; 尚未刷机.
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright (C) 2008-2019 Jerrykuku
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=Argon Theme
|
||||
LUCI_DEPENDS:=+wget +jsonfilter
|
||||
|
||||
PKG_VERSION:=2.4.3
|
||||
PKG_RELEASE:=20250722
|
||||
|
||||
LUCI_MINIFY_CSS:=0
|
||||
|
||||
define Package/luci-theme-argon/conffiles
|
||||
/etc/config/argon
|
||||
/www/luci-static/argon/background/
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,2 @@
|
||||
Drop background here!
|
||||
accept jpg png gif mp4 webm
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="#adaeaf" d="m8,10.033663l-6.898535,-6.013274l-1.060688,0.972974l7.959223,6.986249l7.959223,-6.986249l-1.060688,-0.972974l-6.898535,6.013274z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 225 B |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "Openwrt",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="#888" d="M8,0c-4.355,0-7.898,3.481-7.998,7.812,0.092-3.779,2.966-6.812,6.498-6.812,3.59,0,6.5,3.134,6.5,7,0,0.828,0.672,1.5,1.5,1.5s1.5-0.672,1.5-1.5c0-4.418-3.582-8-8-8zM8,16c4.355,0,7.898-3.481,7.998-7.812-0.092,3.779-2.966,6.812-6.498,6.812-3.59,0-6.5-3.134-6.5-7,0-0.828-0.672-1.5-1.5-1.5s-1.5,0.672-1.5,1.5c0,4.418,3.582,8,8,8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256"
|
||||
style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_2_00000009581766544743910510000007087157279682564742_);}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_3_00000013155245276689480680000010334395393893521599_);}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g id="svg_1">
|
||||
|
||||
<linearGradient id="svg_2_00000043442590260727270070000016472210641679865270_" gradientUnits="userSpaceOnUse" x1="11.1563" y1="247.3437" x2="245.4437" y2="13.0563" gradientTransform="matrix(1 0 0 -1 0 258)">
|
||||
<stop offset="0" style="stop-color:#5E72E4"/>
|
||||
<stop offset="1" style="stop-color:#778AFF"/>
|
||||
</linearGradient>
|
||||
|
||||
<path id="svg_2" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_2_00000043442590260727270070000016472210641679865270_);" d="
|
||||
M36.4,0.2h183.8c19.7,0,35.7,16,35.7,35.7v183.8c0,19.7-16,35.7-35.7,35.7H36.4c-19.7,0-35.7-16-35.7-35.7V35.9
|
||||
C0.7,16.2,16.7,0.2,36.4,0.2z"/>
|
||||
|
||||
<linearGradient id="svg_3_00000010280352489557108120000000938545297310085033_" gradientUnits="userSpaceOnUse" x1="0.7" y1="257.8" x2="0.7" y2="257.8" gradientTransform="matrix(1 0 0 -1 0 258)">
|
||||
<stop offset="0" style="stop-color:#5E72E4"/>
|
||||
<stop offset="1" style="stop-color:#778AFF"/>
|
||||
</linearGradient>
|
||||
|
||||
<path id="svg_3" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#svg_3_00000010280352489557108120000000938545297310085033_);" d="
|
||||
M0.7,0.2"/>
|
||||
</g>
|
||||
<path id="svg_4" class="st2" d="M128.3,45.4c-46.7,0-84.4,37.8-84.4,84.4c0,32.2,18.1,60.2,44.6,74.4c6.8,3.7,15.3-0.2,17.2-7.7
|
||||
l4.3-17.6c1.5-6.2-1-12.6-6.1-16.4c-10-7.4-16.4-19.3-16.4-32.7c0-22.5,18.3-40.7,40.7-40.7c22.5,0,40.7,18.3,40.7,40.7
|
||||
c0,13.4-6.4,25.2-16.4,32.7c-5.1,3.8-7.6,10.2-6.1,16.5l4.4,17.6c1.9,7.5,10.3,11.4,17.2,7.7c26.6-14.2,44.6-42.2,44.6-74.5
|
||||
C212.8,83.3,174.9,45.4,128.3,45.4L128.3,45.4z"/>
|
||||
</g>
|
||||
<circle class="st2" cx="128.3" cy="131.6" r="18.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 938 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024">
|
||||
<path fill="#fff" d="M484.430769 51.2 236.307692 354.461538H118.153846c-43.323077 0-78.769231 35.446154-78.769231 78.769231v157.538462c0 43.323077 35.446154 78.769231 78.769231 78.769231h118.153846L484.430769 972.8c25.6 25.6 66.953846 7.876923 66.953846-27.569231V78.769231c0-35.446154-43.323077-53.169231-66.953846-27.569231zm354.461539 120.123077c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-27.569231 27.569231c-7.876923 7.876923-7.876923 21.661538 0 27.56923C858.584615 299.323077 905.846154 399.753846 905.846154 512c0 112.246154-47.261538 212.676923-122.092308 285.538462-7.876923 7.876923-7.876923 19.692308 0 27.56923l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.569231 0C927.507692 768 984.615385 645.907692 984.615385 512s-55.138462-256-145.723077-340.676923zM714.830769 297.353846c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-27.56923 27.569231c-7.876923 7.876923-7.876923 19.692308 0 27.569231C703.015385 391.876923 728.615385 448.984616 728.615385 512c0 63.015385-27.569231 120.123077-70.892308 159.507692-7.876923 7.876923-7.876923 19.692308 0 27.569231l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.56923 0 57.107692-53.169231 94.523077-129.969231 94.523077-216.615385 0-82.707692-35.446154-159.507692-92.553846-212.676923z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024">
|
||||
<path fill="#fff" d="M484.430769 51.2 236.307692 354.461538H118.153846c-43.323077 0-78.769231 35.446154-78.769231 78.769231v157.538462c0 43.323077 35.446154 78.769231 78.769231 78.769231h118.153846L484.430769 972.8c25.6 25.6 66.953846 7.876923 66.953846-27.569231V78.769231c0-35.446154-43.323077-53.169231-66.953846-27.569231zM882.215385 512l96.492307-96.492308c7.876923-7.876923 7.876923-19.692308 0-27.56923l-27.56923-27.569231c-7.876923-7.876923-19.692308-7.876923-27.569231 0l-96.492308 96.492307-96.492308-96.492307c-7.876923-7.876923-19.692308-7.876923-27.56923 0l-27.569231 27.569231c-7.876923 7.876923-7.876923 19.692308 0 27.56923L771.938462 512l-96.492308 96.492308c-7.876923 7.876923-7.876923 19.692308 0 27.56923l27.569231 27.569231c7.876923 7.876923 19.692308 7.876923 27.56923 0l96.492308-96.492307 96.492308 96.492307c7.876923 7.876923 19.692308 7.876923 27.569231 0l27.56923-27.569231c7.876923-7.876923 7.876923-19.692308 0-27.56923L882.215385 512z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,448 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require ui';
|
||||
|
||||
/**
|
||||
* Native JavaScript slide animation utilities
|
||||
* Replaces jQuery slideUp/slideDown functionality with better performance
|
||||
*/
|
||||
const SlideAnimations = {
|
||||
/**
|
||||
* Animation durations in milliseconds
|
||||
*/
|
||||
durations: {
|
||||
fast: 200,
|
||||
normal: 400,
|
||||
slow: 600
|
||||
},
|
||||
|
||||
/**
|
||||
* Map to track running animations and their cleanup functions
|
||||
*/
|
||||
runningAnimations: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Slide element down (show) with animation
|
||||
* @param {Element} element - DOM element to animate
|
||||
* @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
|
||||
* @param {function} callback - Optional callback function when animation completes
|
||||
*/
|
||||
slideDown: function(element, duration, callback) {
|
||||
if (!element) {
|
||||
console.warn('SlideAnimations.slideDown: No element provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing animation on this element
|
||||
this.stop(element);
|
||||
|
||||
// Convert duration string to milliseconds
|
||||
const animDuration = typeof duration === 'string' ?
|
||||
this.durations[duration] || this.durations.normal :
|
||||
(duration || this.durations.normal);
|
||||
|
||||
// Store original styles
|
||||
const originalStyles = {
|
||||
display: element.style.display,
|
||||
overflow: element.style.overflow,
|
||||
height: element.style.height,
|
||||
transition: element.style.transition
|
||||
};
|
||||
|
||||
// Set initial state for animation
|
||||
element.style.display = 'block';
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.height = '0px';
|
||||
element.style.transition = `height ${animDuration}ms ease-out`;
|
||||
|
||||
// Force reflow to ensure initial state is applied
|
||||
element.offsetHeight;
|
||||
|
||||
// Get the target height
|
||||
const targetHeight = element.scrollHeight;
|
||||
|
||||
// Animate to full height
|
||||
element.style.height = targetHeight + 'px';
|
||||
|
||||
// Set up cleanup function
|
||||
const cleanup = () => {
|
||||
element.style.height = originalStyles.height || '';
|
||||
element.style.overflow = originalStyles.overflow || '';
|
||||
element.style.transition = originalStyles.transition || '';
|
||||
|
||||
// Remove from running animations map
|
||||
this.runningAnimations.delete(element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
try {
|
||||
callback.call(element);
|
||||
} catch (e) {
|
||||
console.error('SlideAnimations callback error:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store cleanup function for potential cancellation
|
||||
const timeoutId = setTimeout(cleanup, animDuration);
|
||||
this.runningAnimations.set(element, { timeoutId, cleanup });
|
||||
},
|
||||
|
||||
/**
|
||||
* Slide element up (hide) with animation
|
||||
* @param {Element} element - DOM element to animate
|
||||
* @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
|
||||
* @param {function} callback - Optional callback function when animation completes
|
||||
*/
|
||||
slideUp: function(element, duration, callback) {
|
||||
if (!element) {
|
||||
console.warn('SlideAnimations.slideUp: No element provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any existing animation on this element
|
||||
this.stop(element);
|
||||
|
||||
// Convert duration string to milliseconds
|
||||
const animDuration = typeof duration === 'string' ?
|
||||
this.durations[duration] || this.durations.normal :
|
||||
(duration || this.durations.normal);
|
||||
|
||||
// Store original styles
|
||||
const originalStyles = {
|
||||
display: element.style.display,
|
||||
overflow: element.style.overflow,
|
||||
height: element.style.height,
|
||||
transition: element.style.transition
|
||||
};
|
||||
|
||||
// Get current height before hiding
|
||||
const currentHeight = element.scrollHeight;
|
||||
|
||||
// Set initial state for animation
|
||||
element.style.overflow = 'hidden';
|
||||
element.style.height = currentHeight + 'px';
|
||||
element.style.transition = `height ${animDuration}ms ease-out`;
|
||||
|
||||
// Force reflow to ensure initial state is applied
|
||||
element.offsetHeight;
|
||||
|
||||
// Animate to zero height
|
||||
element.style.height = '0px';
|
||||
|
||||
// Set up cleanup function
|
||||
const cleanup = () => {
|
||||
element.style.display = 'none';
|
||||
element.style.height = originalStyles.height || '';
|
||||
element.style.overflow = originalStyles.overflow || '';
|
||||
element.style.transition = originalStyles.transition || '';
|
||||
|
||||
// Remove from running animations map
|
||||
this.runningAnimations.delete(element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
try {
|
||||
callback.call(element);
|
||||
} catch (e) {
|
||||
console.error('SlideAnimations callback error:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store cleanup function for potential cancellation
|
||||
const timeoutId = setTimeout(cleanup, animDuration);
|
||||
this.runningAnimations.set(element, { timeoutId, cleanup });
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop all running animations on an element
|
||||
* @param {Element} element - DOM element to stop animations on
|
||||
*/
|
||||
stop: function(element) {
|
||||
if (!element) return;
|
||||
|
||||
const animationData = this.runningAnimations.get(element);
|
||||
if (animationData) {
|
||||
// Clear the timeout
|
||||
clearTimeout(animationData.timeoutId);
|
||||
|
||||
// Run cleanup immediately
|
||||
animationData.cleanup();
|
||||
}
|
||||
|
||||
// Clear transition to immediately stop any CSS animation
|
||||
element.style.transition = '';
|
||||
|
||||
// Force reflow to apply changes immediately
|
||||
element.offsetHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if element has running animation
|
||||
* @param {Element} element - DOM element to check
|
||||
* @returns {boolean} - True if element has running animation
|
||||
*/
|
||||
isAnimating: function(element) {
|
||||
return this.runningAnimations.has(element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Argon Theme Menu Module
|
||||
* Handles rendering and interaction of the main navigation menu and sidebar
|
||||
*/
|
||||
return baseclass.extend({
|
||||
/**
|
||||
* Initialize the menu module
|
||||
* Load menu data and trigger rendering
|
||||
*/
|
||||
__init__: function () {
|
||||
ui.menu.load().then(L.bind(this.render, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Main render function for the menu system
|
||||
* @param {Object} tree - Menu tree structure from LuCI
|
||||
*/
|
||||
render: function (tree) {
|
||||
var node = tree,
|
||||
url = '',
|
||||
children = ui.menu.getChildren(tree);
|
||||
|
||||
// Find and render the active main menu item
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
|
||||
|
||||
if (isActive) {
|
||||
this.renderMainMenu(children[i], children[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Render tab menu if we're deep enough in the navigation hierarchy
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners for sidebar toggle functionality
|
||||
var sidebarToggle = document.querySelector('a.showSide');
|
||||
var darkMask = document.querySelector('.darkMask');
|
||||
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
|
||||
}
|
||||
if (darkMask) {
|
||||
darkMask.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle menu expand/collapse functionality
|
||||
* Manages the sliding animation and active states of menu items
|
||||
* @param {Event} ev - Click event from menu item
|
||||
*/
|
||||
handleMenuExpand: function (ev) {
|
||||
var target = ev.target;
|
||||
var slide = target.parentNode;
|
||||
var slideMenu = target.nextElementSibling;
|
||||
var shouldCollapse = false;
|
||||
|
||||
// Close all currently active submenus
|
||||
var activeMenus = document.querySelectorAll('.main .main-left .nav > li > ul.active');
|
||||
activeMenus.forEach(function (ul) {
|
||||
// Stop any running animations and slide up
|
||||
SlideAnimations.stop(ul);
|
||||
// Remove active classes immediately when starting slideUp animation
|
||||
ul.classList.remove('active');
|
||||
ul.previousElementSibling.classList.remove('active');
|
||||
SlideAnimations.slideUp(ul, 'fast');
|
||||
|
||||
// Check if we're clicking on an already open menu (should collapse it)
|
||||
if (!shouldCollapse && ul === slideMenu) {
|
||||
shouldCollapse = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Exit if there's no submenu to show
|
||||
if (!slideMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the submenu if it's not already open
|
||||
if (!shouldCollapse) {
|
||||
// Find the slide menu within the slide element
|
||||
var slideMenuElement = slide.querySelector(".slide-menu");
|
||||
if (slideMenuElement) {
|
||||
// Add active classes immediately when starting slideDown animation
|
||||
slideMenu.classList.add('active');
|
||||
target.classList.add('active');
|
||||
SlideAnimations.slideDown(slideMenuElement, 'fast');
|
||||
}
|
||||
target.blur(); // Remove focus from the clicked element
|
||||
}
|
||||
|
||||
// Prevent default link behavior and event bubbling
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the main navigation menu
|
||||
* Creates hierarchical menu structure with active states and click handlers
|
||||
* @param {Object} tree - Menu tree node to render
|
||||
* @param {string} url - Base URL for menu items
|
||||
* @param {number} level - Current nesting level (0-based)
|
||||
* @returns {Element} - Generated menu element
|
||||
*/
|
||||
renderMainMenu: function (tree, url, level) {
|
||||
var currentLevel = (level || 0) + 1;
|
||||
var menuContainer = E('ul', { 'class': level ? 'slide-menu' : 'nav' });
|
||||
var children = ui.menu.getChildren(tree);
|
||||
|
||||
// Don't render empty menus or menus deeper than 2 levels
|
||||
if (children.length === 0 || currentLevel > 2) {
|
||||
return E([]);
|
||||
}
|
||||
|
||||
// Generate menu items for each child
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var isActive = (
|
||||
(L.env.dispatchpath[currentLevel] === child.name) &&
|
||||
(L.env.dispatchpath[currentLevel - 1] === tree.name)
|
||||
);
|
||||
|
||||
// Recursively render submenu
|
||||
var submenu = this.renderMainMenu(child, url + '/' + child.name, currentLevel);
|
||||
var hasChildren = submenu.children.length > 0;
|
||||
|
||||
// Determine CSS classes based on state
|
||||
var slideClass = hasChildren ? 'slide' : null;
|
||||
var menuClass = hasChildren ? 'menu' : 'food';
|
||||
|
||||
if (isActive) {
|
||||
menuContainer.classList.add('active');
|
||||
slideClass += " active";
|
||||
menuClass += " active";
|
||||
}
|
||||
|
||||
// Create menu item with link and submenu
|
||||
var menuItem = E('li', { 'class': slideClass }, [
|
||||
E('a', {
|
||||
'href': L.url(url, child.name),
|
||||
'click': (currentLevel === 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null,
|
||||
'class': menuClass,
|
||||
'data-title': child.title.replace(/ /g, "_"), // More robust space replacement
|
||||
}, [_(child.title)]),
|
||||
submenu
|
||||
]);
|
||||
|
||||
menuContainer.appendChild(menuItem);
|
||||
}
|
||||
|
||||
// Append to main menu container if this is the top level
|
||||
if (currentLevel === 1) {
|
||||
var mainMenuElement = document.querySelector('#mainmenu');
|
||||
if (mainMenuElement) {
|
||||
mainMenuElement.appendChild(menuContainer);
|
||||
mainMenuElement.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
return menuContainer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render tab navigation menu
|
||||
* Creates horizontal tab menu for deeper navigation levels
|
||||
* @param {Object} tree - Menu tree node to render
|
||||
* @param {string} url - Base URL for tab items
|
||||
* @param {number} level - Current nesting level (0-based)
|
||||
* @returns {Element} - Generated tab menu element
|
||||
*/
|
||||
renderTabMenu: function (tree, url, level) {
|
||||
var container = document.querySelector('#tabmenu');
|
||||
var currentLevel = (level || 0) + 1;
|
||||
var tabContainer = E('ul', { 'class': 'tabs' });
|
||||
var children = ui.menu.getChildren(tree);
|
||||
var activeNode = null;
|
||||
|
||||
// Don't render empty tab menus
|
||||
if (children.length === 0) {
|
||||
return E([]);
|
||||
}
|
||||
|
||||
// Generate tab items for each child
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var isActive = (L.env.dispatchpath[currentLevel + 2] === child.name);
|
||||
var activeClass = isActive ? ' active' : '';
|
||||
var className = 'tabmenu-item-%s %s'.format(child.name, activeClass);
|
||||
|
||||
var tabItem = E('li', { 'class': className }, [
|
||||
E('a', { 'href': L.url(url, child.name) }, [_(child.title)])
|
||||
]);
|
||||
|
||||
tabContainer.appendChild(tabItem);
|
||||
|
||||
// Store reference to active node for recursive rendering
|
||||
if (isActive) {
|
||||
activeNode = child;
|
||||
}
|
||||
}
|
||||
|
||||
// Append tab container to main tab menu element
|
||||
if (container) {
|
||||
container.appendChild(tabContainer);
|
||||
container.style.display = '';
|
||||
|
||||
// Recursively render nested tab menus if there's an active node
|
||||
if (activeNode) {
|
||||
var nestedTabs = this.renderTabMenu(activeNode, url + '/' + activeNode.name, currentLevel);
|
||||
if (nestedTabs.children.length > 0) {
|
||||
container.appendChild(nestedTabs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tabContainer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle sidebar toggle functionality
|
||||
* Toggles the mobile/responsive sidebar menu visibility
|
||||
* @param {Event} ev - Click event from sidebar toggle button or dark mask
|
||||
*/
|
||||
handleSidebarToggle: function (ev) {
|
||||
var showSideButton = document.querySelector('a.showSide');
|
||||
var sidebar = document.querySelector('#mainmenu');
|
||||
var darkMask = document.querySelector('.darkMask');
|
||||
var scrollbarArea = document.querySelector('.main-right');
|
||||
|
||||
// Check if any required elements are missing
|
||||
if (!showSideButton || !sidebar || !darkMask || !scrollbarArea) {
|
||||
console.warn('Sidebar toggle elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle sidebar visibility and related states
|
||||
if (showSideButton.classList.contains('active')) {
|
||||
// Close sidebar
|
||||
showSideButton.classList.remove('active');
|
||||
sidebar.classList.remove('active');
|
||||
scrollbarArea.classList.remove('active');
|
||||
darkMask.classList.remove('active');
|
||||
} else {
|
||||
// Open sidebar
|
||||
showSideButton.classList.add('active');
|
||||
sidebar.classList.add('active');
|
||||
scrollbarArea.classList.add('active');
|
||||
darkMask.classList.add('active');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// out: false
|
||||
|
||||
// ==========================================================================
|
||||
// CSS Custom Properties for Font Management
|
||||
// ==========================================================================
|
||||
|
||||
@font-face {
|
||||
font-family: 'Google Sans';
|
||||
src: local('Google Sans'),
|
||||
local('GoogleSans-Regular'),
|
||||
url('../fonts/GoogleSans-Regular.woff2') format('woff2'),
|
||||
url('../fonts/GoogleSans-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Logo Font */
|
||||
@font-face {
|
||||
font-family: 'TypoGraphica';
|
||||
src: local('TypoGraphica'),
|
||||
url('../fonts/TypoGraphica.woff2') format('woff2'),
|
||||
url('../fonts/TypoGraphica.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Icon Font */
|
||||
@font-face {
|
||||
font-family: 'argon';
|
||||
src: url('../fonts/argon.woff2') format('woff2'),
|
||||
url('../fonts/argon.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
// out: false
|
||||
// ==========================================================================
|
||||
// Layout Styles
|
||||
// Main layout components for LuCI admin interface
|
||||
// ==========================================================================
|
||||
|
||||
|
||||
// 1. Main Container Layout
|
||||
// ==========================================================================
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
// 2. Left Sidebar Layout
|
||||
// ==========================================================================
|
||||
|
||||
.main-left {
|
||||
width: 15rem;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
z-index: 100;
|
||||
overflow-x: auto;
|
||||
word-break: break-word;
|
||||
background-color: var(--menu-bg-color);
|
||||
box-shadow: rgba(0, 0, 0, 0.75) 0px 0px 15px -5px;
|
||||
transition: all 0.2s;
|
||||
|
||||
// Sidebar Header
|
||||
.sidenav-header {
|
||||
padding: 1.5rem 0.5rem;
|
||||
text-align: center;
|
||||
|
||||
.brand {
|
||||
display: block;
|
||||
margin: 0 2rem;
|
||||
font-size: 1.8rem;
|
||||
font-family: "TypoGraphica";
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
|
||||
.logo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation Menu
|
||||
.nav {
|
||||
margin-top: 0.5rem;
|
||||
|
||||
// Top-level navigation links
|
||||
&>li>a:first-child {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0.1rem 0.5rem;
|
||||
padding: 0.675rem 0 0.675rem 2.5rem;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
|
||||
// Active state
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
|
||||
&::before {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
transform: rotate(90deg);
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Hover state
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
|
||||
&::before {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Icon before text
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 0.8rem;
|
||||
padding-top: 3px;
|
||||
font-family: 'argon' !important;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
content: "\e915";
|
||||
color: var(--primary);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
&>.slide>.menu::before {
|
||||
transition: transform .1s ease-in-out;
|
||||
}
|
||||
|
||||
&>.slide>.menu.active::before {
|
||||
transition: transform .2s ease-in-out;
|
||||
}
|
||||
|
||||
// Navigation list items
|
||||
li {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: var(--menu-color);
|
||||
}
|
||||
|
||||
// Collapsible menu items
|
||||
&.slide {
|
||||
padding: 0;
|
||||
|
||||
ul {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
// Submenu container
|
||||
.slide-menu {
|
||||
margin: 0 0.5rem 0 2.5rem;
|
||||
padding: 0rem 0.5rem;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
border-radius: 0.25rem;
|
||||
background: none;
|
||||
list-style: none;
|
||||
|
||||
a {
|
||||
padding: 0.5rem 0rem;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Hover indicator line
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
content: "";
|
||||
background-color: var(--primary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active submenu item
|
||||
.active {
|
||||
background: none;
|
||||
color: var(--menu-color);
|
||||
|
||||
a {
|
||||
color: var(--menu-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
content: "";
|
||||
background-color: var(--primary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main menu items
|
||||
.menu {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0.1rem 0.5rem;
|
||||
padding: 0.675rem 0 0.675rem 2.5rem;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
|
||||
// Active state
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
|
||||
&::before {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
transform: rotate(90deg);
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Hover state
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
|
||||
&::before {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Icon before text
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 0.8rem;
|
||||
padding-top: 3px;
|
||||
font-family: 'argon' !important;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
content: "\e915";
|
||||
color: var(--primary);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
// Dropdown arrow
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.8rem;
|
||||
font-family: 'argon' !important;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
content: '\e90f';
|
||||
color: #ced4da;
|
||||
text-rendering: auto;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
// Menu-specific icons and colors
|
||||
.menu[data-title="Status"]:before {
|
||||
content: "\e906";
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.menu[data-title="System"]:before {
|
||||
content: "\e90a";
|
||||
color: #fb6340;
|
||||
}
|
||||
|
||||
.menu[data-title="Services"]:before {
|
||||
content: "\e909";
|
||||
color: #11cdef;
|
||||
}
|
||||
|
||||
.menu[data-title="NAS"]:before {
|
||||
content: "\e90c";
|
||||
color: #f3a4b5;
|
||||
}
|
||||
|
||||
.menu[data-title="VPN"]:before {
|
||||
content: "\e90b";
|
||||
color: #8965e0;
|
||||
}
|
||||
|
||||
.menu[data-title="Network"]:before {
|
||||
content: "\e908";
|
||||
color: #8965e0;
|
||||
}
|
||||
|
||||
.menu[data-title="Bandwidth_Monitor"]:before {
|
||||
content: "\e90d";
|
||||
color: #2dce89;
|
||||
}
|
||||
|
||||
.menu[data-title="Docker"]:before {
|
||||
content: "\e911";
|
||||
color: #6699ff;
|
||||
}
|
||||
|
||||
.menu[data-title="Statistics"]:before {
|
||||
content: "\e913";
|
||||
color: #8965e0;
|
||||
}
|
||||
|
||||
.menu[data-title="Control"]:before {
|
||||
content: "\e912";
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.menu[data-title="Asterisk"]:before {
|
||||
content: "\e914";
|
||||
color: #fb6340;
|
||||
}
|
||||
|
||||
// Logout link styling
|
||||
a[data-title="Log_out"],
|
||||
.food[data-title="Log_out"] {
|
||||
&::before {
|
||||
content: "\e907";
|
||||
color: #adb5bd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[style*="overflow: hidden"]>.nav>.slide>.menu::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Scrollbar Styling
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #f6f9fc;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Right Content Area Layout
|
||||
// ==========================================================================
|
||||
|
||||
.main-right {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.2s;
|
||||
|
||||
// Main Content Container
|
||||
&>#maincontent {
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&>.container {
|
||||
margin: 0 1.25rem 1rem 1.25rem;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
// Dashboard Specific Styles
|
||||
.Dashboard {
|
||||
color: var(--gray-dark) !important;
|
||||
|
||||
h3 {
|
||||
color: var(--gray-dark);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.dashboard-bg {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.settings-info {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
|
||||
p span:nth-child(2) {
|
||||
max-height: 18.5px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Header Layout
|
||||
// ==========================================================================
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
color: var(--header-color);
|
||||
|
||||
// Primary background modifier
|
||||
&.bg-primary {
|
||||
background-color: var(--primary) !important;
|
||||
}
|
||||
|
||||
// Header extension background
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
background-color: var(--primary) !important;
|
||||
}
|
||||
|
||||
// Header Content Container
|
||||
.fill {
|
||||
padding: 0.8rem 0;
|
||||
display: flex;
|
||||
border-bottom: 0px solid rgba(255, 255, 255, 0.08) !important;
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
padding: 0 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// Left section (brand and menu toggle)
|
||||
.flex1 {
|
||||
flex: 1;
|
||||
|
||||
.showSide {
|
||||
display: none;
|
||||
font-size: 1.4rem;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: none;
|
||||
padding-left: 1rem;
|
||||
font-size: 1.5rem;
|
||||
font-family: "TypoGraphica";
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// Right section (status indicators)
|
||||
.pull-right {
|
||||
margin-top: 0rem;
|
||||
float: right;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// Status Indicators
|
||||
.status {
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 0 0.25rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
text-shadow: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
span[data-indicator="poll-status"] {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
span[data-style="active"] {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
span[data-style="inactive"] {
|
||||
color: #ffffff !important;
|
||||
background-color: #32325d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Footer Layout
|
||||
// ==========================================================================
|
||||
|
||||
footer {
|
||||
padding: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #aaa;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
>a {
|
||||
color: #aaa;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 6. View
|
||||
// ==========================================================================
|
||||
#view {
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
&>.spinning {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .15);
|
||||
}
|
||||
|
||||
&>div:first-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.875rem;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// out: false
|
||||
// ==========================================================================
|
||||
// Normalize.less
|
||||
// Based on normalize.css for style reset and standardization
|
||||
// ==========================================================================
|
||||
|
||||
// 1. Global Reset
|
||||
// ==========================================================================
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 2. Document Root Elements
|
||||
// ==========================================================================
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: var(--font-family-sans-serif);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
color: var(--gray-dark);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// 3. Semantic Elements
|
||||
// ==========================================================================
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 4. Heading Elements
|
||||
// ==========================================================================
|
||||
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: inherit;
|
||||
font-weight: normal;
|
||||
line-height: 1.1 !important;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0.67em 0;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: thin solid var(--lighter);
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding: 1rem 1.25rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
color: var(--gray-dark);
|
||||
border-radius: 0.25rem;
|
||||
background: var(--white);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0.8755rem 1.25rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
color: var(--gray-dark);
|
||||
border-radius: 0.25rem;
|
||||
background: var(--white);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
color: var(--gray-dark-400);
|
||||
|
||||
em {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 2rem 0 0 0;
|
||||
padding-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// 5. Text Elements
|
||||
// ==========================================================================
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
abbr {
|
||||
cursor: help;
|
||||
text-decoration: underline;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 1px 3px;
|
||||
color: var(--dark);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--lighter);
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 90%;
|
||||
line-height: 1.42857143;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
// 6. Horizontal Rules and Preformatted Text
|
||||
// ==========================================================================
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
margin: 1rem 0;
|
||||
overflow: visible;
|
||||
opacity: 0.1;
|
||||
border-color: var(--lighter);
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
// 7. Media Elements
|
||||
// ==========================================================================
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
// 8. Form Elements
|
||||
// ==========================================================================
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
// Button Styles
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit],
|
||||
button {
|
||||
-webkit-appearance: button;
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
[type=button]::-moz-focus-inner,
|
||||
[type=reset]::-moz-focus-inner,
|
||||
[type=submit]::-moz-focus-inner,
|
||||
button::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type=button]:-moz-focusring,
|
||||
[type=reset]:-moz-focusring,
|
||||
[type=submit]:-moz-focusring,
|
||||
button:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
// Input Styles
|
||||
[type=checkbox],
|
||||
[type=radio] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type=number]::-webkit-inner-spin-button,
|
||||
[type=number]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type=search]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
// checkbox style
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none !important;
|
||||
-webkit-appearance: none !important;
|
||||
border: 1px solid var(--primary);
|
||||
|
||||
width: 1rem !important;
|
||||
height: 1rem !important;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
border: 1px solid var(--primary);
|
||||
background-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 8 8\'%3e%3cpath fill=\'%23fff\' d=\'M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z\'/%3e%3c/svg%3e') !important;
|
||||
background-color: var(--primary);
|
||||
background-size: 70%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
// Select Styles
|
||||
select {
|
||||
padding: 0.36rem 0.8rem;
|
||||
color: var(--gray-dark);
|
||||
border: thin solid var(--lighter);
|
||||
background-color: var(--white);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
// Textarea Styles
|
||||
textarea {
|
||||
padding: 0.25rem;
|
||||
overflow: auto;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Fieldset Styles
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
// 9. Interactive Elements
|
||||
// ==========================================================================
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
// 10. List Elements
|
||||
// ==========================================================================
|
||||
|
||||
ul {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
// 11. State Styles
|
||||
// ==========================================================================
|
||||
|
||||
[disabled="disabled"] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
::selection {
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--lighter);
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
// 12 tools
|
||||
// ==========================================================================
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.pull-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.nowrap:not(.td) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div[style="width:100%;height:300px;border:1px solid #000;background:#fff"] {
|
||||
border: 0 !important;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// out: false
|
||||
/**
|
||||
Pure v3.0.0
|
||||
Copyright 2013 Yahoo!
|
||||
Licensed under the BSD License.
|
||||
https://github.com/pure-css/pure/blob/master/LICENSE
|
||||
*/
|
||||
/**
|
||||
normalize.css v | MIT License | https://necolas.github.io/normalize.css/
|
||||
Copyright (c) Nicolas Gallagher and Jonathan Neal
|
||||
*/
|
||||
/** normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
.pure-g {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-content: flex-start
|
||||
}
|
||||
|
||||
.pure-u {
|
||||
display: inline-block;
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
.pure-u-1,
|
||||
.pure-u-1-1,
|
||||
.pure-u-1-12,
|
||||
.pure-u-1-2,
|
||||
.pure-u-1-24,
|
||||
.pure-u-1-3,
|
||||
.pure-u-1-4,
|
||||
.pure-u-1-5,
|
||||
.pure-u-1-6,
|
||||
.pure-u-1-8,
|
||||
.pure-u-10-24,
|
||||
.pure-u-11-12,
|
||||
.pure-u-11-24,
|
||||
.pure-u-12-24,
|
||||
.pure-u-13-24,
|
||||
.pure-u-14-24,
|
||||
.pure-u-15-24,
|
||||
.pure-u-16-24,
|
||||
.pure-u-17-24,
|
||||
.pure-u-18-24,
|
||||
.pure-u-19-24,
|
||||
.pure-u-2-24,
|
||||
.pure-u-2-3,
|
||||
.pure-u-2-5,
|
||||
.pure-u-20-24,
|
||||
.pure-u-21-24,
|
||||
.pure-u-22-24,
|
||||
.pure-u-23-24,
|
||||
.pure-u-24-24,
|
||||
.pure-u-3-24,
|
||||
.pure-u-3-4,
|
||||
.pure-u-3-5,
|
||||
.pure-u-3-8,
|
||||
.pure-u-4-24,
|
||||
.pure-u-4-5,
|
||||
.pure-u-5-12,
|
||||
.pure-u-5-24,
|
||||
.pure-u-5-5,
|
||||
.pure-u-5-6,
|
||||
.pure-u-5-8,
|
||||
.pure-u-6-24,
|
||||
.pure-u-7-12,
|
||||
.pure-u-7-24,
|
||||
.pure-u-7-8,
|
||||
.pure-u-8-24,
|
||||
.pure-u-9-24 {
|
||||
display: inline-block;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
vertical-align: top;
|
||||
text-rendering: auto
|
||||
}
|
||||
|
||||
.pure-u-1-24 {
|
||||
width: 4.1667%
|
||||
}
|
||||
|
||||
.pure-u-1-12,
|
||||
.pure-u-2-24 {
|
||||
width: 8.3333%
|
||||
}
|
||||
|
||||
.pure-u-1-8,
|
||||
.pure-u-3-24 {
|
||||
width: 12.5%
|
||||
}
|
||||
|
||||
.pure-u-1-6,
|
||||
.pure-u-4-24 {
|
||||
width: 16.6667%
|
||||
}
|
||||
|
||||
.pure-u-1-5 {
|
||||
width: 20%
|
||||
}
|
||||
|
||||
.pure-u-5-24 {
|
||||
width: 20.8333%
|
||||
}
|
||||
|
||||
.pure-u-1-4,
|
||||
.pure-u-6-24 {
|
||||
width: 25%
|
||||
}
|
||||
|
||||
.pure-u-7-24 {
|
||||
width: 29.1667%
|
||||
}
|
||||
|
||||
.pure-u-1-3,
|
||||
.pure-u-8-24 {
|
||||
width: 33.3333%
|
||||
}
|
||||
|
||||
.pure-u-3-8,
|
||||
.pure-u-9-24 {
|
||||
width: 37.5%
|
||||
}
|
||||
|
||||
.pure-u-2-5 {
|
||||
width: 40%
|
||||
}
|
||||
|
||||
.pure-u-10-24,
|
||||
.pure-u-5-12 {
|
||||
width: 41.6667%
|
||||
}
|
||||
|
||||
.pure-u-11-24 {
|
||||
width: 45.8333%
|
||||
}
|
||||
|
||||
.pure-u-1-2,
|
||||
.pure-u-12-24 {
|
||||
width: 50%
|
||||
}
|
||||
|
||||
.pure-u-13-24 {
|
||||
width: 54.1667%
|
||||
}
|
||||
|
||||
.pure-u-14-24,
|
||||
.pure-u-7-12 {
|
||||
width: 58.3333%
|
||||
}
|
||||
|
||||
.pure-u-3-5 {
|
||||
width: 60%
|
||||
}
|
||||
|
||||
.pure-u-15-24,
|
||||
.pure-u-5-8 {
|
||||
width: 62.5%
|
||||
}
|
||||
|
||||
.pure-u-16-24,
|
||||
.pure-u-2-3 {
|
||||
width: 66.6667%
|
||||
}
|
||||
|
||||
.pure-u-17-24 {
|
||||
width: 70.8333%
|
||||
}
|
||||
|
||||
.pure-u-18-24,
|
||||
.pure-u-3-4 {
|
||||
width: 75%
|
||||
}
|
||||
|
||||
.pure-u-19-24 {
|
||||
width: 79.1667%
|
||||
}
|
||||
|
||||
.pure-u-4-5 {
|
||||
width: 80%
|
||||
}
|
||||
|
||||
.pure-u-20-24,
|
||||
.pure-u-5-6 {
|
||||
width: 83.3333%
|
||||
}
|
||||
|
||||
.pure-u-21-24,
|
||||
.pure-u-7-8 {
|
||||
width: 87.5%
|
||||
}
|
||||
|
||||
.pure-u-11-12,
|
||||
.pure-u-22-24 {
|
||||
width: 91.6667%
|
||||
}
|
||||
|
||||
.pure-u-23-24 {
|
||||
width: 95.8333%
|
||||
}
|
||||
|
||||
.pure-u-1,
|
||||
.pure-u-1-1,
|
||||
.pure-u-24-24,
|
||||
.pure-u-5-5 {
|
||||
width: 100%
|
||||
}
|
||||
.col-1 {
|
||||
flex: 1 1 30px !important;
|
||||
}
|
||||
|
||||
.col-2 {
|
||||
flex: 2 2 60px !important;
|
||||
}
|
||||
|
||||
.col-3 {
|
||||
flex: 3 3 90px !important;
|
||||
}
|
||||
|
||||
.col-4 {
|
||||
flex: 4 4 120px !important;
|
||||
}
|
||||
|
||||
.col-5 {
|
||||
flex: 5 5 150px !important;
|
||||
}
|
||||
|
||||
.col-6 {
|
||||
flex: 6 6 180px !important;
|
||||
}
|
||||
|
||||
.col-7 {
|
||||
flex: 7 7 210px !important;
|
||||
}
|
||||
|
||||
.col-8 {
|
||||
flex: 8 8 240px !important;
|
||||
}
|
||||
|
||||
.col-9 {
|
||||
flex: 9 9 270px !important;
|
||||
}
|
||||
|
||||
.col-10 {
|
||||
flex: 10 10 300px !important;
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
// out: false
|
||||
|
||||
// ==========================================================================
|
||||
// Responsive Styles
|
||||
// Mobile-first responsive design for LuCI admin interface
|
||||
// ==========================================================================
|
||||
|
||||
// 1. Browser-Specific Fixes
|
||||
// ==========================================================================
|
||||
|
||||
// Internet Explorer Specific Fixes
|
||||
@media all and (-ms-high-contrast: none) {
|
||||
.main>.main-left>.nav>.slide>.menu::before {
|
||||
top: 30.25%;
|
||||
}
|
||||
|
||||
.main>.main-left>.nav>li:last-child::before {
|
||||
top: 20%;
|
||||
}
|
||||
|
||||
.showSide::before {
|
||||
top: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Large Desktop Screens (1600px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
|
||||
// Header adjustments
|
||||
header>.fill>.container>#logo {
|
||||
margin: 0 2.5rem 0 0.5rem;
|
||||
}
|
||||
|
||||
// Sidebar width
|
||||
.main-left {
|
||||
width: calc(0% + 13rem);
|
||||
}
|
||||
|
||||
// Button and label sizing
|
||||
.btn:not(button),
|
||||
.label {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
// Form field sizing
|
||||
.cbi-value-title {
|
||||
width: 15rem;
|
||||
padding-right: 0.6rem;
|
||||
}
|
||||
|
||||
.cbi-value-field .cbi-dropdown,
|
||||
.cbi-value-field .cbi-input-select,
|
||||
.cbi-value input[type="text"],
|
||||
.cbi-value input[type="password"],
|
||||
.cbi-value textarea {
|
||||
min-width: 18rem;
|
||||
}
|
||||
|
||||
// Specific component adjustments
|
||||
#cbi-firewall-zone .cbi-input-select {
|
||||
min-width: 9rem;
|
||||
}
|
||||
|
||||
.cbi-input-textarea {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.node-admin-status>.main fieldset li>a {
|
||||
padding: 0.3rem 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Standard Desktop Screens (1366px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 1366px) {
|
||||
|
||||
// Header adjustments
|
||||
header>.fill>.container {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// Sidebar width
|
||||
.main-left {
|
||||
width: calc(0% + 13rem);
|
||||
}
|
||||
|
||||
// Tab styling
|
||||
.tabs>li>a,
|
||||
.cbi-tabmenu>li>a {
|
||||
padding: 0.2rem 0.8rem;
|
||||
}
|
||||
|
||||
// Panel and table adjustments
|
||||
.panel-title {
|
||||
font-size: 1.1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.table .cbi-input-text {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Form field sizing
|
||||
.cbi-value-field .cbi-dropdown,
|
||||
.cbi-value-field .cbi-input-select,
|
||||
.cbi-value input[type="text"],
|
||||
.cbi-value input[type="password"] {
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
#cbi-firewall-zone .cbi-input-select {
|
||||
min-width: 5.5rem;
|
||||
}
|
||||
|
||||
// Navigation font sizes
|
||||
.main>.main-left>.nav>li,
|
||||
.main>.main-left>.nav>li>a,
|
||||
.main .main-left .nav>li>a:first-child,
|
||||
.main>.main-left>.nav>.slide>.menu,
|
||||
.main>.main-left>.nav>li>[data-title="Log_out"] {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.main>.main-left>.nav>.slide>.slide-menu>li>a {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// Modal adjustments
|
||||
#modal_overlay {
|
||||
top: 0rem;
|
||||
}
|
||||
|
||||
// Page-specific table fixes
|
||||
[data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table),
|
||||
[data-page="admin-network-firewall-rules"] .table:not(.cbi-section-table),
|
||||
[data-page="admin-network-hosts"] .table,
|
||||
[data-page="admin-network-routes"] .table {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
// Button sizing
|
||||
.btn:not(button),
|
||||
.cbi-button {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Small Desktop/Large Tablet (1152px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
|
||||
// Header adjustments
|
||||
header>.fill>.container>#logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header>.fill>.container>.brand {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Layout adjustments
|
||||
html,
|
||||
.main {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.main>.loading>span {
|
||||
top: 25%;
|
||||
}
|
||||
|
||||
.main-left {
|
||||
width: calc(0% + 13rem);
|
||||
}
|
||||
|
||||
// Login page specific
|
||||
body:not(.logged-in) .showSide {
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.node-main-login>.main .cbi-value-title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
// Form field adjustments
|
||||
.cbi-value-title {
|
||||
width: 12rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.cbi-value-field .cbi-dropdown,
|
||||
.cbi-value-field .cbi-input-select,
|
||||
.cbi-value input[type="text"] {
|
||||
width: 16rem;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
// Password field specific sizing
|
||||
.cbi-value input[name^="pw"],
|
||||
.cbi-value input[data-update="change"]:nth-child(2) {
|
||||
width: 13rem !important;
|
||||
min-width: 13rem;
|
||||
}
|
||||
|
||||
// Code output adjustments
|
||||
#diag-rc-output>pre,
|
||||
#command-rc-output>pre,
|
||||
[data-page="admin-services-wol"] .notice code {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// Table responsive behavior
|
||||
.table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.Interfaces .table {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#packages.table {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
// Table row adjustments
|
||||
.tr {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.Overview .table[width="100%"]>.tr {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.tr.placeholder {
|
||||
border-bottom: thin solid #ddd;
|
||||
}
|
||||
|
||||
.tr.placeholder>.td,
|
||||
#cbi-firewall .tr>.td,
|
||||
#cbi-network .tr:nth-child(2)>.td,
|
||||
.cbi-section #wifi_assoclist_table .tr>.td {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
// Table cell adjustments
|
||||
.th,
|
||||
.td {
|
||||
display: inline-block;
|
||||
align-self: flex-start;
|
||||
flex: 2 2 10%;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.td select,
|
||||
.td input[type="text"] {
|
||||
width: 100%;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.td [data-dynlist]>input,
|
||||
.td input.cbi-input-password {
|
||||
width: calc(100% - 1.5rem);
|
||||
}
|
||||
|
||||
.td[data-type="button"],
|
||||
.td[data-type="fvalue"] {
|
||||
flex: 1 1 12.5%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.th.cbi-value-field,
|
||||
.td.cbi-value-field,
|
||||
.th.cbi-section-table-cell,
|
||||
.td.cbi-section-table-cell {
|
||||
flex-basis: auto;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
// Section table adjustments
|
||||
.cbi-section-table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.td.cbi-value-field,
|
||||
.cbi-section-table-cell {
|
||||
display: inline-block;
|
||||
flex: 10 10 auto;
|
||||
flex-basis: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.td.cbi-section-actions {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
// Hide table headers on mobile
|
||||
.tr.table-titles,
|
||||
.tr.cbi-section-table-titles,
|
||||
.tr.cbi-section-table-descr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tr[data-title]::before,
|
||||
.tr.cbi-section-table-titles.named::before {
|
||||
font-size: 0.9rem;
|
||||
display: block;
|
||||
flex: 1 1 100%;
|
||||
border-bottom: thin solid rgba(0, 0, 0, 0.26);
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.td[data-title],
|
||||
[data-page^="admin-status-realtime"] .td[id] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.td[data-title]::before {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Button spacing
|
||||
.cbi-button+.cbi-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.td.cbi-section-actions>*>*,
|
||||
.td.cbi-section-actions>*>form>* {
|
||||
margin: 2.1px 3px;
|
||||
}
|
||||
|
||||
// Firewall form adjustments
|
||||
.Firewall form {
|
||||
position: static !important;
|
||||
margin: 0 0 2rem 0;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||
|
||||
input {
|
||||
width: 100% !important;
|
||||
margin: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.Firewall .center,
|
||||
.Firewall .center::before {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
// Button sizing
|
||||
.btn:not(button),
|
||||
.cbi-button {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Tablet Portrait (768px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
// Base font size
|
||||
body {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// Progress bar adjustments
|
||||
.cbi-progressbar::after {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
// Mobile sidebar behavior
|
||||
.main-left {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
width: 0;
|
||||
|
||||
&.active {
|
||||
width: 13rem;
|
||||
}
|
||||
}
|
||||
|
||||
.main-right {
|
||||
width: 100%;
|
||||
|
||||
&.active {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.darkMask.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Mobile menu toggle
|
||||
.showSide {
|
||||
padding: 0.1rem;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
display: inline-block !important;
|
||||
|
||||
&::before {
|
||||
font-family: 'argon' !important;
|
||||
font-style: normal !important;
|
||||
font-weight: normal !important;
|
||||
font-variant: normal !important;
|
||||
text-transform: none !important;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
content: "\e20e";
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Brand visibility
|
||||
header>.fill>.container>.flex1>.brand {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Navigation font size
|
||||
.main>.main-left>.nav>.slide>.slide-menu>li>a {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Mobile Portrait (600px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
// Hide mobile elements
|
||||
.mobile-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Content margins
|
||||
#maincontent>.container {
|
||||
margin: 0 1rem 1rem 1rem;
|
||||
}
|
||||
|
||||
// Form adjustments
|
||||
.cbi-value-title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cbi-dynlist p {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
// Prevent horizontal scroll
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// Login page adjustments
|
||||
.node-main-login .main .main-right #maincontent .container .cbi-map .cbi-section .cbi-section-node .cbi-value .cbi-value-field {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.node-main-login footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Tab scrollbar hiding
|
||||
.tabs,
|
||||
.cbi-tabmenu {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
// Form field layout
|
||||
.cbi-value-field,
|
||||
.cbi-value-description {
|
||||
display: block !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
[data-page="admin-system-admin-password"] .cbi-value-field {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
// Modal adjustments
|
||||
.modal.cbi-modal {
|
||||
max-width: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
min-width: 270px;
|
||||
max-width: 600px;
|
||||
min-height: 32px;
|
||||
margin: 5em auto;
|
||||
padding: 1em;
|
||||
border-radius: 3px !important;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
// Dropdown adjustments
|
||||
.cbi-dropdown[open]>ul.dropdown {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
// Login page footer
|
||||
.login-page .login-container footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Small Mobile (480px and below)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
// Hide mobile elements
|
||||
.mobile-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Interface box adjustments for overview page
|
||||
div[style*="display:grid;grid-template-columns:repeat"] {
|
||||
.ifacebox {
|
||||
flex-basis: 80px;
|
||||
|
||||
.ifacebox-body {
|
||||
padding: 0.875rem 0.5rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Login page mobile adjustments
|
||||
.login-page .login-container {
|
||||
margin-left: 0rem !important;
|
||||
width: 100%;
|
||||
|
||||
.login-form {
|
||||
.form-login {
|
||||
.input-group {
|
||||
&::before {
|
||||
color: #525461;
|
||||
}
|
||||
|
||||
input {
|
||||
color: #525461;
|
||||
border-bottom: white 1px solid;
|
||||
border-bottom: var(--white) 1px solid;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Desktop Scrollbar Styling (600px and above)
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--primary);
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// out: false
|
||||
|
||||
// ==========================================================================
|
||||
// Login Page Styles
|
||||
// Optimized with LESS best practices
|
||||
// ==========================================================================
|
||||
|
||||
// Mixins for common patterns
|
||||
.flex-center() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.absolute-full() {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.icon-base() {
|
||||
font-family: 'argon' !important;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
// Background video container
|
||||
.video {
|
||||
.absolute-full();
|
||||
.flex-center();
|
||||
background-color: var(--darker);
|
||||
overflow: hidden;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Volume control button
|
||||
.volume-control {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
z-index: 5000;
|
||||
cursor: pointer;
|
||||
background: url(../img/volume_high.svg) no-repeat center/contain;
|
||||
|
||||
&.mute {
|
||||
background-image: url(../img/volume_off.svg);
|
||||
}
|
||||
}
|
||||
|
||||
// Main background image
|
||||
.main-bg {
|
||||
.absolute-full();
|
||||
background: url(../img/blank.png) no-repeat center/cover;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
// Login container
|
||||
.login-container {
|
||||
z-index: 10;
|
||||
margin-left: 5%;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 26rem;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: var(--white);
|
||||
backdrop-filter: blur(var(--blur-radius));
|
||||
background-color: rgba(244, 245, 247, var(--blur-opacity));
|
||||
box-shadow: rgba(0, 0, 0, 0.75) 0 0 35px -5px;
|
||||
|
||||
// Login form
|
||||
.login-form {
|
||||
.absolute-full();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 26rem;
|
||||
|
||||
|
||||
// Brand/Logo section
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 50px auto 100px 50px;
|
||||
color: var(--default);
|
||||
text-decoration: none;
|
||||
|
||||
.icon {
|
||||
width: 50px;
|
||||
height: auto;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
margin-right: 45px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
font-family: "TypoGraphica";
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Form container
|
||||
.form-login {
|
||||
width: 100%;
|
||||
padding: 20px 50px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.errorbox {
|
||||
padding-bottom: 2rem;
|
||||
text-align: center;
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
// Input group styling
|
||||
.input-group {
|
||||
position: relative;
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
// Icon styling
|
||||
&::before {
|
||||
.icon-base();
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
font-size: 1.5rem;
|
||||
color: var(--default);
|
||||
}
|
||||
|
||||
// Animated border
|
||||
.border {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--primary);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
// Input field
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0.825rem 0;
|
||||
padding: 0.5rem 0.75rem 0.5rem 3rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5em;
|
||||
color: var(--default);
|
||||
background-color: transparent;
|
||||
background-clip: padding-box;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--white);
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 3px 2px rgba(233, 236, 239, 0.05);
|
||||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
&:focus+.border {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.cbi-input-password {
|
||||
position: relative;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
// Icon content
|
||||
&.user-icon::before {
|
||||
content: "\e971";
|
||||
}
|
||||
|
||||
&.pass-icon::before {
|
||||
content: "\e910";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Submit button
|
||||
.cbi-button-apply {
|
||||
width: 100% !important;
|
||||
min-height: 50px;
|
||||
margin: 30px 0 100px;
|
||||
padding: 10px 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--white);
|
||||
text-align: center;
|
||||
letter-spacing: 0.8rem;
|
||||
background-color: var(--primary) !important;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 0 50px 0;
|
||||
transition: all 0.3s ease !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
margin-top: auto;
|
||||
padding: 0 0 30px;
|
||||
color: var(--gray);
|
||||
text-align: center;
|
||||
line-height: 1.6rem;
|
||||
font-size: 0.75rem;
|
||||
|
||||
.ftc {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.luci-link {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$PKG_UPGRADE" != 1 ]; then
|
||||
uci get luci.themes.Argon >/dev/null 2>&1 || \
|
||||
uci batch <<-EOF
|
||||
set luci.themes.Argon=/luci-static/argon
|
||||
set luci.main.mediaurlbase=/luci-static/argon
|
||||
commit luci
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
src="$(uci -q get argon.@global[0].online_wallpaper || echo bing)"
|
||||
case "$src" in
|
||||
bing|none|ghser|unsplash|wallhaven) WEB_PIC_SRC="$src" ;;
|
||||
unsplash_[0-9]*|wallhaven_[0-9]*) WEB_PIC_SRC="$src" ;;
|
||||
*) WEB_PIC_SRC="bing" ;;
|
||||
esac
|
||||
|
||||
API_KEY="$(uci -q get argon.@global[0].use_api_key)"
|
||||
EXACT_RESO="$(uci -q get argon.@global[0].use_exact_resolution || echo '1')"
|
||||
case "$API_KEY" in *[!A-Za-z0-9_.-]*|????????????????????????????????????????????????????????????????*) API_KEY="" ;; esac
|
||||
case "$EXACT_RESO" in 1) ;; *) EXACT_RESO=0 ;; esac
|
||||
|
||||
cache_key="$(echo "$WEB_PIC_SRC" | tr -c 'A-Za-z0-9_-' '_')"
|
||||
CACHE="/var/run/argon_${cache_key}.url"
|
||||
WRLOCK="/var/lock/argon_${cache_key}.lock"
|
||||
|
||||
valid_url() {
|
||||
case "$1" in
|
||||
https://www.bing.com/*|//www.bing.com/*|https://api.vvhan.com/*|https://images.unsplash.com/*|https://wallhaven.cc/*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_pic_url() {
|
||||
case "$WEB_PIC_SRC" in
|
||||
bing)
|
||||
local picpath="$(wget -T3 -qO- "https://www.bing.com/HPImageArchive.aspx?format=js&n=1" | jsonfilter -qe '@.images[0].url' | sed 's/1920x1080/UHD/g')"
|
||||
[ -n "$picpath" ] && echo "//www.bing.com${picpath}"
|
||||
;;
|
||||
ghser)
|
||||
echo "https://api.vvhan.com/api/wallpaper/acg"
|
||||
;;
|
||||
unsplash)
|
||||
if [ -z "$API_KEY" ]; then
|
||||
local pic_id="$(wget -T3 --spider "https://source.unsplash.com/1920x1080/daily?wallpapers" 2>&1 | grep -Eo "photo-\w+-\w+" | head -n1)"
|
||||
[ -n "$pic_id" ] && echo "https://images.unsplash.com/${pic_id}?fm=jpg&fit=crop&w=1920&h=1080"
|
||||
else
|
||||
wget -T3 -qO- "https://api.unsplash.com/photos/random?client_id=${API_KEY}" | jsonfilter -qe '@["urls"]["regular"]'
|
||||
fi
|
||||
;;
|
||||
unsplash_*)
|
||||
local collection_id="${WEB_PIC_SRC#unsplash_}"
|
||||
if [ -z "$API_KEY" ]; then
|
||||
local pic_id="$(wget -T3 --spider "https://source.unsplash.com/collection/${collection_id}/1920x1080" 2>&1 | grep -Eo "photo-\w+-\w+" | head -n1)"
|
||||
[ -n "$pic_id" ] && echo "https://images.unsplash.com/${pic_id}?fm=jpg&fit=crop&w=1920&h=1080"
|
||||
else
|
||||
wget -T3 -qO- "https://api.unsplash.com/photos/random?client_id=${API_KEY}&collections=${collection_id}" | jsonfilter -qe '@["urls"]["regular"]'
|
||||
fi
|
||||
;;
|
||||
wallhaven)
|
||||
wget -T3 -qO- "https://wallhaven.cc/api/v1/search?resolutions=1920x1080&sorting=random" | jsonfilter -qe '@.data[0].path'
|
||||
;;
|
||||
wallhaven_*)
|
||||
local tag_id="${WEB_PIC_SRC#wallhaven_}"
|
||||
local use_reso="resolutions"
|
||||
[ "$EXACT_RESO" -eq 1 ] || use_reso="atleast"
|
||||
[ -z "$API_KEY" ] || API_KEY="apikey=$API_KEY&"
|
||||
wget -T3 -qO- "https://wallhaven.cc/api/v1/search?${API_KEY}q=id%3A${tag_id}&${use_reso}=1920x1080&sorting=random" | jsonfilter -qe '@.data[0].path'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
try_update() {
|
||||
exec 200>"$WRLOCK"
|
||||
if flock -n 200 >/dev/null 2>&1; then
|
||||
local picurl="$(fetch_pic_url)"
|
||||
if valid_url "$picurl"; then
|
||||
printf '%s\n' "$picurl" > "$CACHE"
|
||||
printf '%s\n' "$picurl"
|
||||
elif [ -s "$CACHE" ]; then
|
||||
cat "$CACHE"
|
||||
else
|
||||
: > "$CACHE"
|
||||
fi
|
||||
flock -u 200 >/dev/null 2>&1
|
||||
elif [ -s "$CACHE" ]; then
|
||||
cat "$CACHE"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
"list")
|
||||
json_init; json_add_object "get_url"; json_close_object; json_dump; json_cleanup
|
||||
;;
|
||||
"call")
|
||||
case "$2" in
|
||||
"get_url")
|
||||
read -r input
|
||||
if [ -f "$CACHE" ]; then
|
||||
idle_t="$(($(date '+%s') - $(date -r "$CACHE" '+%s' 2>/dev/null || echo '0')))"
|
||||
if [ -s "$CACHE" ] && [ "$idle_t" -le 43200 ]; then
|
||||
url="$(cat "$CACHE")"
|
||||
valid_url "$url" || url=""
|
||||
json_init; json_add_string "url" "$url"; json_dump; json_cleanup; return 0
|
||||
elif [ ! -s "$CACHE" ] && [ "$idle_t" -le 120 ]; then
|
||||
echo '{ "url": "" }'; return 1
|
||||
fi
|
||||
fi
|
||||
json_init; json_add_string "url" "$(try_update)"; json_dump; json_cleanup; return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"luci-theme-argon": {
|
||||
"description": "Grant UCI access for luci-theme-argon",
|
||||
"read": {
|
||||
"uci": [ "argon" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{#
|
||||
Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template
|
||||
|
||||
luci-theme-argon
|
||||
Copyright 2020 Jerrykuku <jerrykuku@qq.com>
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
https://github.com/jerrykuku/luci-theme-argon/issues
|
||||
|
||||
luci-theme-material:
|
||||
Copyright 2015 Lutty Yang <lutty@wcan.in>
|
||||
|
||||
Agron Theme
|
||||
https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
|
||||
Licensed to the public under the Apache License 2.0
|
||||
-#}
|
||||
|
||||
</div>
|
||||
<footer class="mobile-hide" style="text-wrap: auto">
|
||||
<div class="footer-content" style="display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: end;">
|
||||
<a class="luci-link" href="https://github.com/openwrt/luci" target="_blank">Powered by {{ version.luciname }} ({{ version.luciversion }})</a>
|
||||
|
||||
<span class="footer-separator">|</span>
|
||||
|
||||
<a href="https://github.com/jerrykuku/luci-theme-argon" target="_blank">ArgonTheme {# vPKG_VERSION #}</a>
|
||||
|
||||
<span class="footer-separator">|</span>
|
||||
|
||||
<a class="luci-link" href="{{ version.disturl }}" target="_blank">{{ version.distname }} {{ version.distversion }}-{{ version.distrevision }}</a>
|
||||
|
||||
<ul class="breadcrumb pull-right" id="modemenu" style="display:none"></ul>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// thanks for Jo-Philipp Wich <jow@openwrt.org>
|
||||
var luciLocation = {{ ctx.path }};
|
||||
var winHeight = window.innerHeight;
|
||||
window.addEventListener('resize', function () {
|
||||
var winWidth = window.innerWidth;
|
||||
if(winWidth < 600){
|
||||
var newHeight = window.innerHeight;
|
||||
var keyboradHeight = newHeight - winHeight;
|
||||
var ftcElement = document.querySelector(".ftc");
|
||||
if (ftcElement) {
|
||||
ftcElement.style.bottom = (keyboradHeight + 30) + "px";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript">L.require('menu-argon')</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
{#
|
||||
Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template
|
||||
|
||||
luci-theme-argon
|
||||
Copyright 2020 Jerrykuku <jerrykuku@qq.com>
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
https://github.com/jerrykuku/luci-theme-argon/issues
|
||||
|
||||
luci-theme-material:
|
||||
Copyright 2015 Lutty Yang <lutty@wcan.in>
|
||||
|
||||
Agron Theme
|
||||
https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
|
||||
Licensed to the public under the Apache License 2.0
|
||||
-#}
|
||||
|
||||
</div>
|
||||
<!-- added style="text-wrap: auto" but on this page it is still not the best solution. I will take it to consideration next time :) -->
|
||||
<footer style="text-wrap: auto">
|
||||
<div>
|
||||
<a class="luci-link" href="https://github.com/openwrt/luci" target="_blank">Powered by {{ version.luciname }} ({{ version.luciversion }})</a>
|
||||
<a href="https://github.com/jerrykuku/luci-theme-argon" target="_blank">ArgonTheme {# vPKG_VERSION #}</a>
|
||||
<a class="luci-link" href="{{ version.disturl }}" target="_blank">{{ version.distname }} {{ version.distversion }}-{{ version.distrevision }}</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// thanks for Jo-Philipp Wich <jow@openwrt.org>
|
||||
var luciLocation = {{ ctx.path }};
|
||||
var winHeight = window.innerHeight;
|
||||
window.addEventListener('resize', function () {
|
||||
var winWidth = window.innerWidth;
|
||||
if(winWidth < 600){
|
||||
var newHeight = window.innerHeight;
|
||||
var keyboradHeight = newHeight - winHeight;
|
||||
var ftcElement = document.querySelector(".ftc");
|
||||
if (ftcElement) {
|
||||
ftcElement.style.bottom = (keyboradHeight + 30) + "px";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,181 @@
|
||||
{#
|
||||
Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template
|
||||
|
||||
luci-theme-argon
|
||||
Copyright 2020 Jerrykuku <jerrykuku@qq.com>
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
https://github.com/jerrykuku/luci-theme-argon/issues
|
||||
|
||||
luci-theme-material:
|
||||
Copyright 2015 Lutty Yang <lutty@wcan.in>
|
||||
|
||||
Argon Theme
|
||||
https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
|
||||
Licensed to the public under the Apache License 2.0
|
||||
-#}
|
||||
|
||||
{%
|
||||
import { readfile, access } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { srand } from 'math';
|
||||
import { getuid, getspnam } from 'luci.core';
|
||||
|
||||
const boardinfo = ubus.call("system", "board");
|
||||
const hostname = striptags(boardinfo?.hostname ?? '?');
|
||||
|
||||
let cfg = cursor();
|
||||
|
||||
//send as HTML5
|
||||
http.prepare_content('text/html; charset=UTF-8');
|
||||
|
||||
srand(+substr(reverse(time() + ""), 0, 8));
|
||||
|
||||
//Custom settings
|
||||
let mode = 'normal';
|
||||
let bar_color = '#5e72e4';
|
||||
let primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity, blur_opacity_dark;
|
||||
const dark_css = readfile('/www/luci-static/argon/css/dark.css');
|
||||
if (access('/etc/config/argon')) {
|
||||
primary = cfg.get_first('argon', 'global', 'primary');
|
||||
dark_primary = cfg.get_first('argon', 'global', 'dark_primary');
|
||||
blur_radius = cfg.get_first('argon', 'global', 'blur');
|
||||
blur_radius_dark = cfg.get_first('argon', 'global', 'blur_dark');
|
||||
blur_opacity = cfg.get_first('argon', 'global', 'transparency');
|
||||
blur_opacity_dark = cfg.get_first('argon', 'global', 'transparency_dark');
|
||||
mode = cfg.get_first('argon', 'global', 'mode');
|
||||
bar_color = (mode == 'dark') ? dark_primary : primary;
|
||||
}
|
||||
|
||||
-%}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ dispatcher.lang }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>{{ hostname }}{{ node?.title ? ` - ${striptags(node.title)}` : '' }} - LuCI</title>
|
||||
|
||||
<!-- SEO and Description -->
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<!-- Security -->
|
||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN">
|
||||
<meta http-equiv="X-XSS-Protection" content="1; mode=block">
|
||||
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
|
||||
|
||||
<!-- Mobile and PWA -->
|
||||
<meta name="format-detection" content="telephone=no, email=no">
|
||||
<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="apple-mobile-web-app-title" content="{{ hostname }} - LuCI">
|
||||
|
||||
<!-- Theme and Colors -->
|
||||
<meta name="theme-color" content="{{ bar_color }}">
|
||||
<meta name="msapplication-TileColor" content="{{ bar_color }}">
|
||||
<meta name="msapplication-TileImage" content="{{ media }}/icon/ms-icon-144x144.png">
|
||||
<meta name="application-name" content="{{ hostname }} - LuCI">
|
||||
|
||||
<!-- Icons and Manifest -->
|
||||
<link rel="icon" type="image/x-icon" href="{{ media }}/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ media }}/icon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ media }}/icon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ media }}/icon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{{ media }}/icon/android-icon-192x192.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ media }}/icon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ media }}/icon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{ media }}/icon/apple-icon-144x144.png">
|
||||
<link rel="manifest" href="{{ media }}/icon/manifest.json" crossorigin="use-credentials">
|
||||
|
||||
<link rel="stylesheet" href="{{ media }}/css/cascade.css{# ?v=PKG_VERSION #}">
|
||||
<style title="text/css">
|
||||
{% if (mode == 'normal'): %}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
{{ dark_css }}
|
||||
}
|
||||
{% endif %}
|
||||
{% if (mode == 'dark'): %}
|
||||
{{ dark_css }}
|
||||
{% endif -%}
|
||||
{% if (access('/etc/config/argon')): %}
|
||||
:root {
|
||||
--primary: {{ primary }};
|
||||
--dark-primary: {{ dark_primary }};
|
||||
--blur-radius:{{ blur_radius }}px;
|
||||
--blur-opacity:{{ blur_opacity }};
|
||||
--blur-radius-dark:{{ blur_radius_dark }}px;
|
||||
--blur-opacity-dark:{{ blur_opacity_dark }};
|
||||
}
|
||||
{% endif -%}
|
||||
</style>
|
||||
<!-- Stylesheets -->
|
||||
{% if (node?.css): %}
|
||||
<link rel="stylesheet" href="{{ resource }}/{{ node.css}}">
|
||||
{% endif -%}
|
||||
{% if (css): %}
|
||||
<style title="text/css">
|
||||
{{ css }}
|
||||
</style>
|
||||
{% endif -%}
|
||||
<script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}?v={{ version.luciversion }}"></script>
|
||||
<script src="{{ resource }}/cbi.js?v={{ version.luciversion }}"></script>
|
||||
<script src="{{ resource }}/luci.js?v={{ version.luciversion }}"></script>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="lang_{{ dispatcher.lang }} {{ node?.title ? ` - ${striptags(node.title)}` : '' }} {% if (ctx.authsession): %}logged-in{% endif %}"
|
||||
data-page="{{ entityencode(join('-', ctx.request_path), true) }}">
|
||||
|
||||
<div class="main">
|
||||
<div class="main-left" id="mainmenu" style="display:none">
|
||||
<div class="sidenav-header d-flex align-items-center">
|
||||
<a class="brand" href="#">{{ hostname }}</a>
|
||||
<div class="ml-auto">
|
||||
<!-- Sidenav toggler -->
|
||||
<div class="sidenav-toggler d-none d-xl-block active" data-action="sidenav-unpin"
|
||||
data-target="#sidenav-main">
|
||||
<div class="sidenav-toggler-inner">
|
||||
<i class="sidenav-toggler-line"></i>
|
||||
<i class="sidenav-toggler-line"></i>
|
||||
<i class="sidenav-toggler-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-right">
|
||||
<header class="bg-primary">
|
||||
<div class="fill">
|
||||
<div class="container">
|
||||
<div class="flex1">
|
||||
<a class="showSide"></a>
|
||||
<a class="brand" href="#">{{ hostname }}</a>
|
||||
</div>
|
||||
<div class="status" id="indicators"></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="darkMask"></div>
|
||||
<div id="maincontent">
|
||||
<div class="container">
|
||||
{% if (getuid() == 0 && getspnam('root')?.pwdp === ''): %}
|
||||
<div class="alert-message error">
|
||||
<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 %}
|
||||
|
||||
<noscript>
|
||||
<div class="alert-message error">
|
||||
<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>
|
||||
@@ -0,0 +1,125 @@
|
||||
{#
|
||||
Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template
|
||||
|
||||
luci-theme-argon
|
||||
Copyright 2020 Jerrykuku <jerrykuku@qq.com>
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
https://github.com/jerrykuku/luci-theme-argon/issues
|
||||
|
||||
luci-theme-material:
|
||||
Copyright 2015 Lutty Yang <lutty@wcan.in>
|
||||
|
||||
Argon Theme
|
||||
https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
|
||||
Licensed to the public under the Apache License 2.0
|
||||
-#}
|
||||
|
||||
{%
|
||||
import { readfile, access } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { srand } from 'math';
|
||||
|
||||
const boardinfo = ubus.call("system", "board");
|
||||
const hostname = striptags(boardinfo?.hostname ?? '?');
|
||||
|
||||
let cfg = cursor();
|
||||
|
||||
//send as HTML5
|
||||
http.prepare_content('text/html; charset=UTF-8');
|
||||
|
||||
srand(+substr(reverse(time() + ""), 0, 8));
|
||||
|
||||
//Custom settings
|
||||
let mode = 'normal';
|
||||
let bar_color = '#5e72e4';
|
||||
let primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity, blur_opacity_dark;
|
||||
const dark_css = readfile('/www/luci-static/argon/css/dark.css');
|
||||
if (access('/etc/config/argon')) {
|
||||
primary = cfg.get_first('argon', 'global', 'primary');
|
||||
dark_primary = cfg.get_first('argon', 'global', 'dark_primary');
|
||||
blur_radius = cfg.get_first('argon', 'global', 'blur');
|
||||
blur_radius_dark = cfg.get_first('argon', 'global', 'blur_dark');
|
||||
blur_opacity = cfg.get_first('argon', 'global', 'transparency');
|
||||
blur_opacity_dark = cfg.get_first('argon', 'global', 'transparency_dark');
|
||||
mode = cfg.get_first('argon', 'global', 'mode');
|
||||
bar_color = (mode == 'dark') ? dark_primary : primary;
|
||||
}
|
||||
|
||||
-%}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ dispatcher.lang }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>{{ hostname }}{{ node?.title ? ` - ${striptags(node.title)}` : '' }} - LuCI</title>
|
||||
|
||||
<!-- SEO and Description -->
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<!-- Security -->
|
||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN">
|
||||
<meta http-equiv="X-XSS-Protection" content="1; mode=block">
|
||||
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
|
||||
|
||||
<!-- Mobile and PWA -->
|
||||
<meta name="format-detection" content="telephone=no, email=no">
|
||||
<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="apple-mobile-web-app-title" content="{{ hostname }} - LuCI">
|
||||
|
||||
<!-- Theme and Colors -->
|
||||
<meta name="theme-color" content="{{ bar_color }}">
|
||||
<meta name="msapplication-TileColor" content="{{ bar_color }}">
|
||||
<meta name="msapplication-TileImage" content="{{ media }}/icon/ms-icon-144x144.png">
|
||||
<meta name="application-name" content="{{ hostname }} - LuCI">
|
||||
|
||||
<!-- Icons and Manifest -->
|
||||
<link rel="icon" type="image/x-icon" href="{{ media }}/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ media }}/icon/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ media }}/icon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ media }}/icon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{{ media }}/icon/android-icon-192x192.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="{{ media }}/icon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ media }}/icon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{ media }}/icon/apple-icon-144x144.png">
|
||||
<link rel="manifest" href="{{ media }}/icon/manifest.json" crossorigin="use-credentials">
|
||||
|
||||
<link rel="stylesheet" href="{{ media }}/css/cascade.css{# ?v=PKG_VERSION #}">
|
||||
<style title="text/css">
|
||||
{% if (mode == 'normal'): %}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
{{ dark_css }}
|
||||
}
|
||||
{% endif %}
|
||||
{% if (mode == 'dark'): %}
|
||||
{{ dark_css }}
|
||||
{% endif -%}
|
||||
{% if (access('/etc/config/argon')): %}
|
||||
:root {
|
||||
--primary: {{ primary }};
|
||||
--dark-primary: {{ dark_primary }};
|
||||
--blur-radius:{{ blur_radius }}px;
|
||||
--blur-opacity:{{ blur_opacity }};
|
||||
--blur-radius-dark:{{ blur_radius_dark }}px;
|
||||
--blur-opacity-dark:{{ blur_opacity_dark }};
|
||||
}
|
||||
{% endif -%}
|
||||
</style>
|
||||
<link rel="shortcut icon" href="{{ media }}/favicon.ico">
|
||||
{% if (node?.css): %}
|
||||
<link rel="stylesheet" href="{{ resource }}/{{ node.css}}">
|
||||
{% endif -%}
|
||||
{% if (css): %}
|
||||
<style title="text/css">
|
||||
{{ css }}
|
||||
</style>
|
||||
{% endif -%}
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -0,0 +1,12 @@
|
||||
{#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{%
|
||||
if (!ctx.template_header_sent) {
|
||||
include("themes/" + theme + "/header_login");
|
||||
ctx.template_header_sent = true;
|
||||
}
|
||||
%}
|
||||
@@ -0,0 +1,165 @@
|
||||
{#
|
||||
Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-bootstrap and MUI and Argon Template
|
||||
|
||||
luci-theme-argon
|
||||
Copyright 2020 Jerryk <jerrykuku@gmail.com>
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
https://github.com/jerrykuku/luci-theme-argon/issues
|
||||
|
||||
luci-theme-bootstrap:
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Copyright 2012 David Menting <david@nut-bolt.nl>
|
||||
|
||||
MUI:
|
||||
https://github.com/muicss/mui
|
||||
|
||||
Argon Theme
|
||||
https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
|
||||
Licensed to the public under the Apache License 2.0
|
||||
-#}
|
||||
|
||||
{% include("themes/" + theme + "/out_header_login") %}
|
||||
{%
|
||||
import * as fs from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { rand } from 'math';
|
||||
|
||||
let cfg = cursor();
|
||||
|
||||
//Fetch Local Background Media
|
||||
|
||||
const imageTypes = " jpg jpeg png gif webp ";
|
||||
const videoTypes = " mp4 webm ";
|
||||
const allTypes = imageTypes + videoTypes;
|
||||
|
||||
function fetchMedia(path, themeDir) {
|
||||
let backgroundTable = [];
|
||||
for (f in (fs.lsdir(path))) {
|
||||
let ext = lc(split(f, '.')?.[1]);
|
||||
if (ext && index(allTypes, " " + ext + " ") != -1) {
|
||||
let bg = {};
|
||||
bg.type = ext;
|
||||
bg.url = themeDir + f;
|
||||
push(backgroundTable, bg);
|
||||
}
|
||||
}
|
||||
return backgroundTable;
|
||||
}
|
||||
|
||||
function selectBackground(themeDir) {
|
||||
let bgUrl = media + "/img/bg1.jpg";
|
||||
let remoteBgUrl = "";
|
||||
let backgroundType = "Image";
|
||||
let mimeType = "";
|
||||
|
||||
if (fs.access("/etc/config/argon")) {
|
||||
let online_wallpaper = cfg.get_first('argon', 'global', 'online_wallpaper') ?? (cfg.get_first('argon', 'global', 'bing_background') == '1' ? 'bing' : null);
|
||||
if (online_wallpaper && online_wallpaper != "none") {
|
||||
const picurl = ubus.call("luci.argon_wallpaper", "get_url") ?? {};
|
||||
if (picurl?.url && match(picurl.url, /^(https?:\/\/|\/\/)www\.bing\.com\/|^(https?:\/\/)api\.vvhan\.com\/|^(https?:\/\/)images\.unsplash\.com\/|^(https?:\/\/)wallhaven\.cc\//))
|
||||
remoteBgUrl = picurl.url;
|
||||
}
|
||||
}
|
||||
|
||||
let background = fetchMedia("/www" + themeDir, themeDir);
|
||||
// Priority: local uploaded backgrounds win; only fall through to
|
||||
// the remote Bing/preload URL when no local files exist.
|
||||
if (length(background) > 0) {
|
||||
let currentBg = background[(rand() % length(background))];
|
||||
bgUrl = currentBg.url;
|
||||
if (index(videoTypes, " " + currentBg.type + " ") != -1) {
|
||||
backgroundType = "Video";
|
||||
mimeType = "video/" + currentBg.type;
|
||||
}
|
||||
}
|
||||
|
||||
return {bgUrl, remoteBgUrl, backgroundType, mimeType};
|
||||
}
|
||||
|
||||
const boardinfo = ubus.call("system", "board");
|
||||
const hostname = striptags(boardinfo?.hostname ?? '?');
|
||||
const themeDir = media + "/background/";
|
||||
const bgArry = selectBackground(themeDir);
|
||||
|
||||
%}
|
||||
<!-- Login Page Start -->
|
||||
<div class="login-page">
|
||||
{% if ( bgArry.backgroundType == "Video" ): %}
|
||||
<!-- Video Player Start -->
|
||||
<div class="video">
|
||||
<video autoplay loop muted id="video">
|
||||
<source src="{{ bgArry.bgUrl }}" type="{{ bgArry.mimeType }}">
|
||||
</video>
|
||||
</div>
|
||||
<div class="volume-control mute"></div>
|
||||
<script>
|
||||
document.querySelector(".volume-control").addEventListener("click", function(){
|
||||
if(this.classList.contains("mute")){
|
||||
this.classList.remove("mute");
|
||||
document.getElementById("video").muted = false;
|
||||
}else{
|
||||
this.classList.add("mute");
|
||||
document.getElementById("video").muted = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- Video Player End -->
|
||||
{% else %}
|
||||
<!-- Image Background Start -->
|
||||
<div class="main-bg" id="main-bg" style="background-image:url({{ bgArry.bgUrl }})"></div>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
var remote = "{{ bgArry.remoteBgUrl }}";
|
||||
if (!remote)
|
||||
return;
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
var el = document.getElementById("main-bg");
|
||||
if (el)
|
||||
el.style.backgroundImage = "url(" + remote + ")";
|
||||
};
|
||||
img.onerror = function() {};
|
||||
img.src = remote;
|
||||
})();
|
||||
//]]></script>
|
||||
<!-- Image Background End -->
|
||||
{% endif %}
|
||||
<!-- Login Container Start -->
|
||||
<div class="login-container">
|
||||
<div class="login-form">
|
||||
<!-- Logo Start -->
|
||||
<a class="brand" href="/"><img src="{{ media }}/img/argon.svg" class="icon">
|
||||
<span class="brand-text">{{ hostname }}</span>
|
||||
</a>
|
||||
<!-- Logo End -->
|
||||
<!-- Login Form Start -->
|
||||
<form class="form-login" method="post" action="{{ http.getenv("REQUEST_URI") }}">
|
||||
|
||||
{%- if (fuser): %}
|
||||
<div class="errorbox">{{ _('Invalid username and/or password! Please try again.') }}</div>
|
||||
{% endif -%}
|
||||
|
||||
<div class="input-container">
|
||||
<div class="input-group user-icon">
|
||||
<input class="cbi-input-user" id="cbi-input-user" type="text" name="luci_username" value="{{ entityencode(duser, true) }}" />
|
||||
<label class="border" for="cbi-input-user"></label>
|
||||
</div>
|
||||
<div class="input-group pass-icon">
|
||||
<input class="cbi-input-password" id="cbi-input-password" type="password" name="luci_password" />
|
||||
<label class="border" for="cbi-input-password"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="{{ _('Log in') }}" class="cbi-button cbi-button-apply" />
|
||||
</div>
|
||||
</form>
|
||||
<!-- Login Form End -->
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var input = document.getElementsByName('luci_password')[0];
|
||||
if (input)
|
||||
input.focus();
|
||||
//]]></script>
|
||||
{% include("themes/" + theme + "/footer_login") %}
|
||||