💋 Sync 2026-10-04 04:16:09
No files matched your search
@@ -0,0 +1,2 @@
|
||||
Drop background here!
|
||||
accept jpg png gif mp4 webm
|
||||
@@ -0,0 +1,963 @@
|
||||
/**
|
||||
* NoobWrt is a clean HTML5 theme for LuCI. It is based on luci-theme-material and NoobWrt Template
|
||||
*
|
||||
* luci-theme-noobwrt
|
||||
* Copyright 2025 Jerryk <jerrykuku@gmail.com>
|
||||
*
|
||||
* Have a bug? Please create an issue here on GitHub!
|
||||
* https://github.com/jerrykuku/luci-theme-noobwrt/issues
|
||||
*
|
||||
* luci-theme-bootstrap:
|
||||
* Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
* Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
* Copyright 2012 David Menting <david@nut-bolt.nl>
|
||||
*
|
||||
* MUI:
|
||||
* https://github.com/muicss/mui
|
||||
*
|
||||
* luci-theme-material:
|
||||
* https://github.com/LuttyYang/luci-theme-material/
|
||||
*
|
||||
* Agron Theme
|
||||
* https://demos.creative-tim.com/argon-dashboard/index.html
|
||||
*
|
||||
* Login background
|
||||
* https://unsplash.com/
|
||||
*
|
||||
* Licensed to the public under the Apache License 2.0
|
||||
*/
|
||||
body {
|
||||
background: #1e1e1e;
|
||||
color: #cccccc;
|
||||
}
|
||||
.spinning::before {
|
||||
filter: invert(1);
|
||||
}
|
||||
.login-page .login-container .login-form .login-card {
|
||||
background-color: rgba(18, 18, 28, 0.92);
|
||||
-webkit-backdrop-filter: blur(var(--blur-radius-dark)) brightness(0.9);
|
||||
backdrop-filter: blur(var(--blur-radius-dark)) brightness(0.9);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.login-page .login-container .login-form .login-card .login-hero {
|
||||
background: linear-gradient(135deg, rgba(33, 33, 44, 0.95), rgba(26, 27, 40, 0.95));
|
||||
}
|
||||
.login-page .login-container .login-form .login-card .login-hero .hero-particles span {
|
||||
background: rgba(124, 118, 255, 0.4);
|
||||
box-shadow: 0 0 18px rgba(124, 118, 255, 0.45);
|
||||
}
|
||||
.login-page .login-container .login-form .login-card .login-hero .hero-icon {
|
||||
background: rgba(124, 118, 255, 0.08);
|
||||
border: 1px solid rgba(124, 118, 255, 0.28);
|
||||
box-shadow: 0 0 28px rgba(124, 118, 255, 0.35), inset 0 0 18px rgba(124, 118, 255, 0.12);
|
||||
}
|
||||
.login-page .login-container .login-form .brand {
|
||||
color: #adb5bd;
|
||||
}
|
||||
.login-page .login-container .login-form .brand-text-only {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
.login-page .login-container .login-form .login-subtitle {
|
||||
color: #b7bdcc;
|
||||
font-weight: 500;
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .errorbox {
|
||||
color: #fecaca;
|
||||
background: rgba(220, 38, 38, 0.15);
|
||||
border: 1px solid rgba(248, 113, 113, 0.3);
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .input-group::before {
|
||||
color: #a1a8b8;
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .input-group input {
|
||||
background-color: rgba(30, 30, 40, 0.9) !important;
|
||||
color: #e5e7eb;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
border-radius: 999px !important;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35), 0 10px 20px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .input-group input:focus {
|
||||
border-color: rgba(99, 102, 241, 0.65) !important;
|
||||
background-color: rgba(36, 36, 48, 0.95) !important;
|
||||
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .input-group input::placeholder {
|
||||
color: #8f96a8;
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .cbi-button-apply {
|
||||
background: linear-gradient(135deg, #7c76ff, #5b8dff) !important;
|
||||
}
|
||||
.login-page .login-container .login-form .form-login .cbi-button-apply:hover,
|
||||
.login-page .login-container .login-form .form-login .cbi-button-apply:focus {
|
||||
opacity: 0.95;
|
||||
}
|
||||
header::after {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
.main .main-left {
|
||||
background-color: #333333 !important;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.main .main-left .sidenav-header .brand {
|
||||
color: #cccccc;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu .active a {
|
||||
color: #cccccc;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu .active a::after {
|
||||
background-color: #cccccc !important;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu .active::after {
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu li a {
|
||||
color: #cccccc;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu li a:hover {
|
||||
background: none !important;
|
||||
}
|
||||
.main .main-left .nav .slide .slide-menu li::after {
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
.main .main-left .nav .slide .menu.active {
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.main .main-left .nav .slide .menu.active a::after {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
.main .main-left .nav li a {
|
||||
color: #cccccc !important;
|
||||
}
|
||||
.main .main-left .nav li a:hover {
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.main .main-left::-webkit-scrollbar-thumb {
|
||||
background-color: #252526 !important;
|
||||
}
|
||||
.main .main-left::-webkit-scrollbar-track {
|
||||
background-color: #333;
|
||||
}
|
||||
.main .main-right {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
h2 {
|
||||
color: #ccc;
|
||||
background: #333333;
|
||||
}
|
||||
h3 {
|
||||
color: #ccc;
|
||||
border-bottom: 0;
|
||||
background: #333333;
|
||||
}
|
||||
h4 {
|
||||
color: #8C6900;
|
||||
}
|
||||
abbr {
|
||||
color: var(--dark-primary) !important;
|
||||
}
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
color: #a5b2ff;
|
||||
}
|
||||
input:-webkit-autofill {
|
||||
background-color: #3c3c3c !important;
|
||||
}
|
||||
#channel_graph {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.cbi-value-field .cbi-input-apply,
|
||||
.cbi-button-apply,
|
||||
.cbi-button-edit {
|
||||
color: #fff !important;
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
border-color: #483d8b !important;
|
||||
border-color: var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-section em {
|
||||
color: #ccc;
|
||||
}
|
||||
header.bg-primary {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
.cbi-map-descr {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-section {
|
||||
background: none;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.panel-title {
|
||||
color: #ccc;
|
||||
background-color: #333333;
|
||||
}
|
||||
div > table > tbody > tr:nth-of-type(2n),
|
||||
div > .table > .tr:nth-of-type(2n) {
|
||||
background-color: #252526;
|
||||
}
|
||||
table > tbody > tr > td,
|
||||
table > tfoot > tr > td,
|
||||
table > thead > tr > td {
|
||||
color: #cccccc;
|
||||
}
|
||||
fieldset > table > tbody > tr:nth-of-type(2n) {
|
||||
background-color: #252526;
|
||||
}
|
||||
table > tbody > tr > td,
|
||||
table > tfoot > tr > td,
|
||||
table > thead > tr > td {
|
||||
border-top: 1px solid #252526;
|
||||
}
|
||||
#swaptotal > div > div,
|
||||
#swapfree > div > div,
|
||||
#memfree > div > div,
|
||||
#membuff > div > div,
|
||||
#conns > div > div,
|
||||
#memtotal > div > div {
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
#swaptotal > div > div > div > small,
|
||||
#swapfree > div > div > div > small,
|
||||
#memfree > div > div > div > small,
|
||||
#membuff > div > div > div > small,
|
||||
#conns > div > div > div > small,
|
||||
#memtotal > div > div > div > small {
|
||||
color: #ccc !important;
|
||||
}
|
||||
.node-system-packages > .main .cbi-section-node:first-child .cbi-value-last {
|
||||
line-height: 1.8em;
|
||||
}
|
||||
.node-system-packages > .main .cbi-section-node:first-child .cbi-value-last div[style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080"] {
|
||||
border: 1px solid #999999 !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.node-system-packages > .main .cbi-section-node:first-child .cbi-value-last div[style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080"] div {
|
||||
background-color: #ba8b00 !important;
|
||||
}
|
||||
table > tbody > tr > th,
|
||||
table > tfoot > tr > th,
|
||||
table > thead > tr > th {
|
||||
background-color: #252526;
|
||||
border-bottom: black 1px solid !important;
|
||||
}
|
||||
.tr[data-title]::before,
|
||||
.tr.cbi-section-table-titles.named::before {
|
||||
background-color: #252526;
|
||||
}
|
||||
tr > td,
|
||||
tr > th,
|
||||
.tr > .td,
|
||||
.tr > .th,
|
||||
.cbi-section-table-row::before,
|
||||
#cbi-wireless > #wifi_assoclist_table > .tr:nth-child(2) {
|
||||
border-top: 0;
|
||||
}
|
||||
.cbi-rowstyle-2 {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.cbi-rowstyle-1 {
|
||||
background-color: #252526;
|
||||
}
|
||||
.cbi-rowstyle-2 .cbi-button-up,
|
||||
.cbi-rowstyle-2 .cbi-button-down,
|
||||
body:not(.Interfaces) .cbi-rowstyle-2:first-child {
|
||||
background-color: #666666 !important;
|
||||
}
|
||||
.cbi-section > h3:first-child,
|
||||
.panel-title,
|
||||
h3 {
|
||||
color: #ccc;
|
||||
}
|
||||
h4 {
|
||||
background-color: #1e1e1f;
|
||||
}
|
||||
.cbi-progressbar {
|
||||
background: #282a2c;
|
||||
}
|
||||
.cbi-progressbar div {
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {
|
||||
background-color: #1e1e1f;
|
||||
}
|
||||
.cbi-button {
|
||||
color: #ffffff !important;
|
||||
background-color: #483d8b;
|
||||
background-color: var(--dark-primary);
|
||||
}
|
||||
.cbi-section-node {
|
||||
background: none;
|
||||
}
|
||||
abbr {
|
||||
color: #5e72e4;
|
||||
}
|
||||
div > table > tbody > tr:nth-of-type(2n),
|
||||
div > .table > .tbody > .tr:nth-of-type(2n) {
|
||||
background-color: #252526;
|
||||
}
|
||||
#content_syslog {
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
#syslog {
|
||||
color: #ccc;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
#iwsvg,
|
||||
#iwsvg2,
|
||||
#bwsvg {
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
.tabs {
|
||||
background-color: #252526;
|
||||
}
|
||||
.tabs li[class~="active"],
|
||||
.tabs li:hover {
|
||||
cursor: pointer;
|
||||
color: #ccc !important;
|
||||
}
|
||||
.tabs li[class~="active"] a,
|
||||
.tabs li:hover a {
|
||||
color: #ccc !important;
|
||||
}
|
||||
.tabs > li[class~="active"] > a {
|
||||
color: #ccc;
|
||||
}
|
||||
.tabs > li[class~="active"],
|
||||
.tabs > li:hover {
|
||||
border-bottom: 0.18751rem solid #483d8b;
|
||||
border-bottom: 0.18751rem solid var(--dark-primary);
|
||||
color: var(--dark-primary) !important;
|
||||
background-color: #181819;
|
||||
}
|
||||
.cbi-tabmenu > li > a,
|
||||
.tabs > li > a {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-tabmenu > li > a:hover,
|
||||
.tabs > li > a:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-tabmenu > li {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
.cbi-tabmenu li[class~="cbi-tab"] a {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-tabmenu > li:hover {
|
||||
color: #ccc;
|
||||
background: #2d2d2d;
|
||||
border-bottom: 0.18751rem solid var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-tabmenu > li[class~="cbi-tab"] {
|
||||
background-color: #181819;
|
||||
border-bottom: 0.18751rem solid var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-tabcontainer > .cbi-value:nth-of-type(2n) {
|
||||
background-color: #252526;
|
||||
}
|
||||
.cbi-value-title {
|
||||
color: #ccc;
|
||||
}
|
||||
select,
|
||||
input {
|
||||
color: #ccc;
|
||||
background-color: transparent !important;
|
||||
border: 1px solid #252526;
|
||||
box-shadow: none;
|
||||
}
|
||||
select:not([multiple="multiple"]):focus,
|
||||
input:focus {
|
||||
border-color: #483d8b !important;
|
||||
border-color: var(--dark-primary) !important;
|
||||
outline: 0;
|
||||
}
|
||||
select:not([multiple="multiple"]):focus,
|
||||
input:not(.cbi-button):focus,
|
||||
.cbi-dropdown:focus {
|
||||
border-color: #5e72e4;
|
||||
border-color: var(--dark-primary) !important;
|
||||
}
|
||||
select {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
#cbi-dropbear h2,
|
||||
#cbi-dropbear .cbi-map-descr,
|
||||
#cbi-dropbear .cbi-map-descr abbr,
|
||||
#cbi-rc h2,
|
||||
#cbi-rc .cbi-map-descr,
|
||||
#cbi-distfeedconf h2,
|
||||
#cbi-distfeedconf .cbi-map-descr,
|
||||
#cbi-customfeedconf h2,
|
||||
#cbi-customfeedconf .cbi-map-descr,
|
||||
#cbi-download h2,
|
||||
#cbi-filelist h2 {
|
||||
color: #ccc !important;
|
||||
}
|
||||
.cbi-value-field > ul > li .ifacebadge {
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
.cbi-section-descr {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-input-textarea {
|
||||
background-color: #1e1e1e;
|
||||
color: #ccc;
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
textarea:focus-visible {
|
||||
border: 1px solid var(--dark-primary);
|
||||
}
|
||||
.cbi-section-remove:nth-of-type(2n),
|
||||
.cbi-section-node:nth-of-type(2n) {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.cbi-section[id] .cbi-section-remove:nth-of-type(4n+3),
|
||||
.cbi-section[id] .cbi-section-node:nth-of-type(4n+4) {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.node-system-packages > .main table tr td:nth-last-child(1) {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-tooltip {
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
.ifacebox {
|
||||
background-color: transparent !important;
|
||||
border: 1px solid #1e1e1e;
|
||||
}
|
||||
.ifacebox-head {
|
||||
color: #ccc;
|
||||
background: #666;
|
||||
}
|
||||
.ifacebox-body {
|
||||
background-color: #333;
|
||||
}
|
||||
.zonebadge strong {
|
||||
color: #333;
|
||||
}
|
||||
.zonebadge > .ifacebadge {
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
div.cbi-value var,
|
||||
td.cbi-value-field var {
|
||||
color: #5e72e4;
|
||||
}
|
||||
#diag-rc-output > pre {
|
||||
color: #ccc;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.node-services-vssr .block {
|
||||
background-color: #3c3c3c !important;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.node-services-vssr .block h4 {
|
||||
color: #ccc !important;
|
||||
}
|
||||
.node-services-vssr .status-bar {
|
||||
color: #ccc;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.node-services-vssr .cbi-section-table-row {
|
||||
color: #ccc;
|
||||
background-color: #3c3c3c !important;
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.node-services-vssr .cbi-section-table-row.fast {
|
||||
background: #483d8b !important;
|
||||
background: var(--dark-primary) !important;
|
||||
color: #fff;
|
||||
}
|
||||
.node-services-vssr .ssr-button {
|
||||
color: #ccc;
|
||||
}
|
||||
.node-services-vssr .incon:nth-child(2) {
|
||||
border-right: #1e1e1e 1px solid;
|
||||
}
|
||||
#xhr_poll_status > .label.success {
|
||||
color: #ccc !important;
|
||||
background-color: darkolivegreen !important;
|
||||
}
|
||||
.notice {
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-section.fade-in .cbi-title > div:last-child span {
|
||||
background: none!important;
|
||||
color: #ced4da;
|
||||
}
|
||||
.cbi-input-find,
|
||||
.cbi-input-save,
|
||||
.cbi-button-add,
|
||||
.cbi-button-save,
|
||||
.cbi-button-find,
|
||||
.cbi-input-reload,
|
||||
.cbi-button-reload {
|
||||
color: #fff !important;
|
||||
background: darkolivegreen !important;
|
||||
border-color: darkolivegreen !important;
|
||||
}
|
||||
.cbi-button-reset,
|
||||
.cbi-input-remove {
|
||||
color: #fff !important;
|
||||
background-color: darkorange !important;
|
||||
border-color: darkorange !important;
|
||||
}
|
||||
.cbi-page-actions .cbi-button-apply,
|
||||
.cbi-section-actions .cbi-button-edit,
|
||||
.cbi-button-edit.important,
|
||||
.cbi-button-apply.important,
|
||||
.cbi-button-reload.important,
|
||||
.cbi-button-action.important {
|
||||
border: 1px #483d8b solid !important;
|
||||
border: 1px var(--dark-primary) solid !important;
|
||||
}
|
||||
.btn[value="Dismiss"],
|
||||
.cbi-button[value="Terminate"],
|
||||
.cbi-button[value="Reset"],
|
||||
.cbi-button[value="Disabled"],
|
||||
.cbi-button[onclick^="iface_reconnect"],
|
||||
.cbi-button[onclick="handleReset(event)"],
|
||||
.cbi-button-neutral[value="Disable"] {
|
||||
color: #fff;
|
||||
border: thin solid darkorange !important;
|
||||
background-color: darkorange !important;
|
||||
}
|
||||
fieldset[id^="cbi-apply-"] {
|
||||
background-color: #333333;
|
||||
}
|
||||
#detail-bubble > div {
|
||||
border: 1px solid #ccc;
|
||||
background: #252525;
|
||||
}
|
||||
.ifacebox-head.active {
|
||||
background-color: var(--dark-primary) !important;
|
||||
}
|
||||
header .fill .status span[data-style="active"] {
|
||||
color: #ccc !important;
|
||||
background-color: darkolivegreen !important;
|
||||
}
|
||||
#cbi-wireless .td,
|
||||
#cbi-network .tr:first-child > .td,
|
||||
.table[width="100%"] > .tr:first-child > .td,
|
||||
[data-page="admin-network-diagnostics"] .tr > .td,
|
||||
.tr.table-titles > .th,
|
||||
.tr.cbi-section-table-titles > .th {
|
||||
background-color: #252526;
|
||||
border-bottom: black 1px solid !important;
|
||||
}
|
||||
.network-status-table .ifacebox-body .ifacebadge {
|
||||
background-color: #252526;
|
||||
border-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
td > .ifacebadge,
|
||||
.td > .ifacebadge {
|
||||
color: #fff;
|
||||
background-color: #483d8b;
|
||||
background-color: var(--dark-primary);
|
||||
border: 0;
|
||||
}
|
||||
.alert,
|
||||
.alert-message {
|
||||
background-color: #333;
|
||||
}
|
||||
.alert-message.warning {
|
||||
background-color: #986400 !important;
|
||||
}
|
||||
.alert.error,
|
||||
.alert-message.error {
|
||||
background-color: #784f00;
|
||||
}
|
||||
.alert h4,
|
||||
.alert-message h4 {
|
||||
background-color: #784f00;
|
||||
color: #ffbf00;
|
||||
}
|
||||
.alert-message [class="btn"] {
|
||||
background-color: #777777;
|
||||
color: #ccc;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > ins {
|
||||
border-color: #00ad00;
|
||||
background-color: #688668;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > del {
|
||||
border-color: #c60000;
|
||||
background-color: #896565;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var {
|
||||
background-color: #333;
|
||||
border-color: #666;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var ins {
|
||||
background-color: #688668;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var del {
|
||||
background-color: #896565;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list > var {
|
||||
background-color: #333;
|
||||
border-color: #666;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list > var del {
|
||||
background-color: #896565;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list > var ins {
|
||||
background-color: #688668;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list > ins {
|
||||
border-color: #00ad00;
|
||||
background-color: #688668;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list > del {
|
||||
border-color: #c60000;
|
||||
background-color: #896565;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list + .right .btn {
|
||||
color: #ccc;
|
||||
}
|
||||
.uci-dialog .cbi-section .uci-change-list + .right .cbi-button {
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.btn.danger,
|
||||
.cbi-section-remove > .cbi-button,
|
||||
.cbi-button-remove,
|
||||
.cbi-button-reset,
|
||||
.cbi-button-negative,
|
||||
.cbi-button[value="Stop"],
|
||||
.cbi-button[value="Kill"],
|
||||
.cbi-button[onclick="reboot(this)"],
|
||||
.cbi-button-neutral[value="Restart"] {
|
||||
border: thin solid darkorange !important;
|
||||
background-color: darkorange !important;
|
||||
}
|
||||
.cbi-section,
|
||||
.cbi-section-error,
|
||||
#iptables,
|
||||
.Firewall form,
|
||||
#cbi-network > .cbi-section-node,
|
||||
#cbi-wireless > .cbi-section-node,
|
||||
#cbi-wireless > #wifi_assoclist_table,
|
||||
[data-tab-title],
|
||||
[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
|
||||
[data-page="admin-system-opkg"] #maincontent > .container {
|
||||
background: #1e1e1e !important;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
div[style="width:100%;height:300px;border:1px solid #000;background:#fff"] {
|
||||
background: transparent !important;
|
||||
}
|
||||
[data-page="admin-system-admin"] .cbi-map h2,
|
||||
[data-page="admin-system-admin-password"] .cbi-map h2,
|
||||
[data-page="admin-system-admin"] .cbi-map .cbi-map-descr,
|
||||
[data-page="admin-system-admin-password"] .cbi-map .cbi-map-descr {
|
||||
color: #ccc;
|
||||
}
|
||||
[data-page="admin-system-flash"] .modal label > input[type="checkbox"] {
|
||||
vertical-align: text-top;
|
||||
top: auto;
|
||||
border-color: #fff !important;
|
||||
}
|
||||
[data-page="admin-system-flash"] .modal .btn {
|
||||
white-space: normal !important;
|
||||
background-color: darkseagreen;
|
||||
}
|
||||
[data-page="admin-system-flash"] .modal .alert-message {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
[data-page="admin-system-flash"] .modal .danger {
|
||||
border: thin solid darkorange !important;
|
||||
background-color: darkorange !important;
|
||||
}
|
||||
.cbi-value input[type="password"] + .cbi-button-neutral {
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
border-color: var(--dark-primary) !important;
|
||||
}
|
||||
.btn,
|
||||
button,
|
||||
select,
|
||||
input,
|
||||
.cbi-dropdown {
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.cbi-dropdown .preview {
|
||||
color: #ccc;
|
||||
}
|
||||
.cbi-section-table-row {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
.modal {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
.cbi-button-positive {
|
||||
color: #fff !important;
|
||||
background-color: darkolivegreen !important;
|
||||
}
|
||||
[data-page="admin-system-flash"] legend {
|
||||
color: #ccc;
|
||||
}
|
||||
.logout:before {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
.cbi-dropdown[open] {
|
||||
border-color: #483d8b !important;
|
||||
border-color: var(--dark-primary) !important;
|
||||
}
|
||||
.cbi-dropdown[open] > ul.dropdown {
|
||||
background: #252526 !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow: none;
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.cbi-dropdown[open] > ul.dropdown li {
|
||||
color: #ffffff;
|
||||
border-bottom: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.cbi-dropdown[open] > ul.dropdown > li[selected] {
|
||||
background-color: #483d8b !important;
|
||||
background-color: var(--dark-primary) !important;
|
||||
border-bottom: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.cbi-dropdown[open] > ul.dropdown > li.focus {
|
||||
background: #483d8b;
|
||||
background: var(--dark-primary);
|
||||
outline: none;
|
||||
}
|
||||
.ifacebadge {
|
||||
background-color: #333333;
|
||||
}
|
||||
.cbi-dynlist > .item > span {
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
.cbi-page-actions .cbi-button-apply,
|
||||
.cbi-section-actions .cbi-button-edit,
|
||||
.cbi-button-edit,
|
||||
.cbi-button-apply,
|
||||
.cbi-button-reload,
|
||||
.cbi-button-action,
|
||||
.cbi-button[value="Submit"],
|
||||
.cbi-button[value="Upload"],
|
||||
.cbi-button[value$="Apply"],
|
||||
.cbi-button[onclick="addKey(event)"] {
|
||||
background: #483d8b !important;
|
||||
background: var(--dark-primary) !important;
|
||||
border-color: var(--dark-primary) !important;
|
||||
}
|
||||
.btn.primary,
|
||||
.cbi-page-actions .cbi-button-save,
|
||||
.cbi-page-actions .cbi-button-apply + .cbi-button-save,
|
||||
.cbi-button-add,
|
||||
.cbi-button-save,
|
||||
.cbi-button-positive,
|
||||
.cbi-button-link,
|
||||
.cbi-button[value="Enable"],
|
||||
.cbi-button[value="Scan"],
|
||||
.cbi-button[value^="Back"],
|
||||
.cbi-button-neutral[onclick="handleConfig(event)"] {
|
||||
background: #483d8b;
|
||||
background: var(--dark-primary);
|
||||
}
|
||||
[data-page="admin-system-opkg"] h2 {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-system-startup"] textarea {
|
||||
color: #ccc;
|
||||
background-color: transparent;
|
||||
}
|
||||
[data-page="admin-system-startup"] textarea:focus-visible {
|
||||
border: 1px solid var(--dark-primary);
|
||||
}
|
||||
[data-page="admin-network-firewall-custom"] #view p textarea,
|
||||
[data-page="admin-status-routes"] #view p textarea,
|
||||
[data-page="admin-system-crontab"] #view p textarea {
|
||||
color: #ccc;
|
||||
background-color: transparent;
|
||||
}
|
||||
#view > .spinning {
|
||||
background: #333333 !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
@media screen and (min-width: 600px) {
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--dark-primary);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--dark-primary);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: var(--dark-primary);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
.node-status-iptables > .main div > .cbi-map > form {
|
||||
background-color: #1e1e1e;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard h3,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard h3 {
|
||||
color: #ccc;
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard hr,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard hr {
|
||||
border-top: 1px solid #ffffff;
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard .dashboard-bg,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard .dashboard-bg {
|
||||
background-color: #333333;
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard img.svgmonochrome,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard img.svgmonochrome {
|
||||
filter: invert(90%);
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard tr,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard tr {
|
||||
border-top: thin solid #4d4d4d;
|
||||
}
|
||||
[data-page=""] .main-right > #maincontent .Dashboard tr:last-child,
|
||||
[data-page="admin-dashboard"] .main-right > #maincontent .Dashboard tr:last-child {
|
||||
border-bottom: thin solid #4d4d4d;
|
||||
}
|
||||
[data-page="admin-system-fileassistant"] .fb-container .panel-container {
|
||||
border-bottom-color: #3c3c3c !important;
|
||||
}
|
||||
[data-page="admin-system-fileassistant"] .fb-container td[class$="-icon"]::before {
|
||||
filter: invert(0.7);
|
||||
}
|
||||
[data-page="admin-system-fileassistant"] div#list-content table.cbi-section-table tbody tr:nth-child(1) td.parent-icon {
|
||||
background: #1e1e1e !important;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] .oc {
|
||||
--bg-light: #1e1e1e !important;
|
||||
--bg-gray: #1e1e1e !important;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] .oc .developer-container {
|
||||
background: transparent !important;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] .config-upload-content {
|
||||
background: #1e1e1e !important;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] .cbi-tabmenu::-webkit-scrollbar-thumb {
|
||||
background-color: #5b5b5b;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] .cbi-tabmenu::-webkit-scrollbar-track {
|
||||
background-color: rgba(60, 60, 60, 0.75);
|
||||
}
|
||||
[data-page^="admin-services-openclash"] #tab {
|
||||
border-color: #3c3c3c;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] #diag-rc-output > pre,
|
||||
[data-page^="admin-services-openclash"] #dns-rc-output > pre {
|
||||
color: #ccc;
|
||||
border: 1px solid #3c3c3c !important;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
[data-page^="admin-services-openclash"] img[src$="arrow-clockwise-light.svg"],
|
||||
[data-page^="admin-services-openclash"] img[src$="wrench-light.svg"],
|
||||
[data-page^="admin-services-openclash"] img[src$="eye-light.svg"] {
|
||||
filter: invert(0.9);
|
||||
}
|
||||
[data-page="admin-network-diagnostics"] .cbi-section {
|
||||
background: #252526 !important;
|
||||
}
|
||||
[data-page="admin-network-diagnostics"] textarea {
|
||||
background: transparent;
|
||||
border-radius: 0.25rem;
|
||||
color: #ccc;
|
||||
border: 1px solid #3c3c3c !important;
|
||||
}
|
||||
[data-page="admin-network-diagnostics"] .tr > .td {
|
||||
background-color: #252526 !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 144, 240, 144"] {
|
||||
background-color: #497e49 !important;
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 240, 144, 144;"] {
|
||||
background-color: #9c4f4f !important;
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 238, 238, 238;"] {
|
||||
background-color: #666 !important;
|
||||
}
|
||||
[data-page="admin-network-network"] #modal_overlay > .modal.cbi-modal > div > p > textarea {
|
||||
border: 1px solid #3c3c3c !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
[data-page="admin-network-network"] #modal_overlay div[data-tab="peers"] div[data-name="_peers"] .ifacebadge code {
|
||||
color: #fff !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
[data-page="admin-network-network"] #modal_overlay > .modal.cbi-modal > div > p > textarea {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] {
|
||||
--zone-color-rgb: 156, 79, 79 !important;
|
||||
}
|
||||
[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] {
|
||||
--zone-color-rgb: 112, 112, 112 !important;
|
||||
}
|
||||
[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] em {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-firewall-rules"] #cbi-firewall-rule > table > .cbi-section-table-row[data-title]::before {
|
||||
background-color: #252526 !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"],
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] {
|
||||
--zone-color-rgb: 156, 79, 79 !important;
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong,
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"],
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] {
|
||||
--zone-color-rgb: 73, 126, 73 !important;
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] strong,
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] strong {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"],
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] {
|
||||
--zone-color-rgb: 112, 112, 112 !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong,
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong + span,
|
||||
[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong + span {
|
||||
color: #ccc !important;
|
||||
}
|
||||
[data-page="admin-system-commands"] .commandbox,
|
||||
[data-page="admin-system-commands-dashboard"] .commandbox {
|
||||
border-bottom: thin solid #333;
|
||||
background: #252526;
|
||||
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.2), 0 1px 2px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.btn {
|
||||
background-color: #707070;
|
||||
color: #fff;
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.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: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.4 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: 4.6 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 |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 938 B |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="900" zoomAndPan="magnify" viewBox="0 0 675 675.000002" height="900" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><g/><clipPath id="438b7503c7"><rect x="0" width="499" y="0" height="573"/></clipPath></defs><g transform="matrix(1, 0, 0, 1, 89, 90)"><g clip-path="url(#438b7503c7)"><g fill="#ff914d" fill-opacity="1"><g transform="translate(3.55271, 443.299439)"><g><path d="M 169.222656 -154.308594 C 169.222656 -154.308594 299.4375 -77.441406 439.402344 30.976562 L 454.890625 21.796875 C 454.890625 21.796875 438.253906 -110.710938 438.253906 -204.214844 C 438.253906 -330.984375 459.480469 -414.734375 459.480469 -414.734375 C 436.535156 -410.71875 416.457031 -409.574219 397.527344 -409.574219 C 360.242188 -409.574219 329.839844 -413.015625 298.289062 -418.179688 C 298.289062 -418.179688 315.496094 -325.824219 315.496094 -263.871094 L 315.496094 -238.632812 L 314.351562 -238.632812 C 302.878906 -242.644531 225.4375 -303.453125 154.878906 -354.503906 C 106.121094 -389.496094 65.96875 -432.519531 65.96875 -432.519531 L 49.332031 -427.929688 C 49.332031 -427.929688 51.054688 -345.898438 51.054688 -293.125 C 51.054688 -72.277344 28.109375 6.308594 28.109375 6.308594 C 53.347656 2.292969 81.457031 1.722656 106.695312 1.722656 C 133.65625 1.722656 165.78125 2.867188 193.886719 8.03125 C 193.886719 8.03125 169.222656 -45.890625 169.222656 -137.097656 Z M 169.222656 -154.308594 "/></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 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.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 |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,514 @@
|
||||
'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.
|
||||
* When prefers-reduced-motion is active, all animations use 0ms.
|
||||
*/
|
||||
durations: {
|
||||
fast: 200,
|
||||
normal: 400,
|
||||
slow: 600
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the user has requested reduced motion via OS/browser setting.
|
||||
*/
|
||||
prefersReducedMotion: function() {
|
||||
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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; skip animation for reduced-motion users
|
||||
const animDuration = this.prefersReducedMotion() ? 0 :
|
||||
(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; skip animation for reduced-motion users
|
||||
const animDuration = this.prefersReducedMotion() ? 0 :
|
||||
(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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NoobWrt 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;
|
||||
var url = '';
|
||||
|
||||
this.renderModeMenu(node);
|
||||
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
// Persist open section: clear since we just closed everything
|
||||
try {
|
||||
localStorage.setItem('noobwrt.sidenav.open', '[]');
|
||||
} catch (e) {}
|
||||
|
||||
// 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');
|
||||
|
||||
// Persist which section is open
|
||||
try {
|
||||
var openTitle = target.getAttribute('data-title');
|
||||
if (openTitle) {
|
||||
localStorage.setItem('noobwrt.sidenav.open', JSON.stringify([openTitle]));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
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 = '';
|
||||
|
||||
// Restore previously opened submenus from localStorage
|
||||
var openSections = [];
|
||||
try {
|
||||
openSections = JSON.parse(localStorage.getItem('noobwrt.sidenav.open') || '[]');
|
||||
} catch (e) {}
|
||||
|
||||
menuContainer.querySelectorAll('.nav > li.slide > a.menu').forEach(function(anchor) {
|
||||
var title = anchor.getAttribute('data-title');
|
||||
var slideMenu = anchor.nextElementSibling;
|
||||
if (!slideMenu) return;
|
||||
|
||||
var isActive = anchor.classList.contains('active');
|
||||
var wasOpen = openSections.indexOf(title) !== -1;
|
||||
|
||||
if (isActive || wasOpen) {
|
||||
anchor.classList.add('active');
|
||||
slideMenu.classList.add('active');
|
||||
slideMenu.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll the active menu item into view smoothly
|
||||
var activeItem = mainMenuElement.querySelector('.nav > li > a.active, .nav > li > a.menu.active');
|
||||
if (activeItem) {
|
||||
setTimeout(function() {
|
||||
activeItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menuContainer;
|
||||
},
|
||||
|
||||
renderModeMenu: function (tree) {
|
||||
var menu = document.querySelector('#modemenu');
|
||||
var children = ui.menu.getChildren(tree);
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
|
||||
if (i > 0)
|
||||
menu.appendChild(E([], ['\u00a0|\u00a0']));
|
||||
menu.appendChild(E('li', {}, [
|
||||
E('a', {
|
||||
'href': L.url(children[i].name),
|
||||
'class': isActive ? 'active' : null
|
||||
}, [_(children[i].title)])
|
||||
]));
|
||||
if (isActive)
|
||||
this.renderMainMenu(children[i], children[i].name);
|
||||
}
|
||||
if (menu.children.length > 1)
|
||||
menu.style.display = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 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,306 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callSetCustomWallpaper = rpc.declare({
|
||||
object: 'luci.noobwrt_wallpaper',
|
||||
method: 'set_custom',
|
||||
expect: { result: -1 }
|
||||
});
|
||||
|
||||
var callRemoveCustomWallpaper = rpc.declare({
|
||||
object: 'luci.noobwrt_wallpaper',
|
||||
method: 'remove_custom',
|
||||
expect: { result: -1 }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return uci.load('noobwrt').catch(function () {
|
||||
/* /etc/config/noobwrt is absent (fresh install or missing defaults).
|
||||
* Seed an in-memory named section 'global' so form.NamedSection
|
||||
* can render without firing uci/get RPCs that return code 4.
|
||||
* uci.add() returns the internal sid; setting .name promotes it
|
||||
* to a named section that form.NamedSection('global') will find. */
|
||||
var sid = uci.add('noobwrt', 'global');
|
||||
uci.set('noobwrt', sid, '.name', 'global');
|
||||
uci.set('noobwrt', sid, 'mode', 'normal');
|
||||
uci.set('noobwrt', sid, 'primary', '#5e72e4');
|
||||
uci.set('noobwrt', sid, 'dark_primary', '#7c8ff5');
|
||||
uci.set('noobwrt', sid, 'blur', '10');
|
||||
uci.set('noobwrt', sid, 'blur_dark', '10');
|
||||
uci.set('noobwrt', sid, 'transparency', '0.8');
|
||||
uci.set('noobwrt', sid, 'transparency_dark', '0.8');
|
||||
uci.set('noobwrt', sid, 'online_wallpaper', 'none');
|
||||
uci.set('noobwrt', sid, 'auto_dark_mode', '0');
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('noobwrt', _('NoobWrt Theme Settings'),
|
||||
_('Customize the appearance, wallpaper, background effects, and toolbar panel of the NoobWrt LuCI theme.'));
|
||||
|
||||
/* ============================================================
|
||||
* SECTION: Global – tabbed (Appearance / Wallpaper / Effects / Toolbar / About)
|
||||
* ============================================================ */
|
||||
s = m.section(form.NamedSection, 'global', 'global');
|
||||
s.addremove = false;
|
||||
s.anonymous = true;
|
||||
|
||||
s.tab('appearance', _('Appearance'));
|
||||
s.tab('wallpaper', _('Wallpaper'));
|
||||
s.tab('effects', _('Background Effects'));
|
||||
s.tab('toolbar', _('Toolbar Panel'));
|
||||
s.tab('about', _('About'));
|
||||
|
||||
/* ---- Appearance ---- */
|
||||
o = s.taboption('appearance', form.Flag, 'auto_dark_mode',
|
||||
_('Automatic Light / Dark Mode'),
|
||||
_('Automatically switch to Dark mode from 6 PM to 6 AM and Light mode from 6 AM to 6 PM. When enabled, the schedule overrides the manual toolbar toggle.'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.taboption('appearance', form.ListValue, 'mode',
|
||||
_('Default Theme Mode'),
|
||||
_('Theme applied on page load when Automatic mode is disabled. Users can also toggle it manually using the toolbar sun/moon button.'));
|
||||
o.value('normal', _('Light'));
|
||||
o.value('dark', _('Dark'));
|
||||
|
||||
o = s.taboption('appearance', form.Value, 'primary',
|
||||
_('Accent Color — Light Mode'),
|
||||
_('Primary accent color used throughout the light theme.'));
|
||||
o.placeholder = '#5e72e4';
|
||||
o.rmempty = false;
|
||||
o.renderWidget = function(section_id, option_index, cfgvalue) {
|
||||
var node = form.Value.prototype.renderWidget.apply(this, arguments);
|
||||
var input = node.querySelector('input');
|
||||
if (input) {
|
||||
input.style.cssText = 'width:140px;display:inline-block;vertical-align:middle;margin-right:6px';
|
||||
var picker = document.createElement('input');
|
||||
picker.type = 'color';
|
||||
picker.value = (cfgvalue && cfgvalue !== this.placeholder) ? cfgvalue : this.placeholder;
|
||||
picker.style.cssText = 'width:40px;height:32px;padding:2px;border:1px solid #ccc;border-radius:4px;cursor:pointer;vertical-align:middle';
|
||||
picker.addEventListener('input', function() { input.value = picker.value; input.dispatchEvent(new Event('input', { bubbles: true })); });
|
||||
input.addEventListener('input', function() { if (/^#[0-9a-fA-F]{6}$/.test(input.value)) picker.value = input.value; });
|
||||
node.appendChild(picker);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
o = s.taboption('appearance', form.Value, 'dark_primary',
|
||||
_('Accent Color — Dark Mode'),
|
||||
_('Primary accent color used throughout the dark theme.'));
|
||||
o.placeholder = '#7c8ff5';
|
||||
o.rmempty = false;
|
||||
o.renderWidget = function(section_id, option_index, cfgvalue) {
|
||||
var node = form.Value.prototype.renderWidget.apply(this, arguments);
|
||||
var input = node.querySelector('input');
|
||||
if (input) {
|
||||
input.style.cssText = 'width:140px;display:inline-block;vertical-align:middle;margin-right:6px';
|
||||
var picker = document.createElement('input');
|
||||
picker.type = 'color';
|
||||
picker.value = (cfgvalue && cfgvalue !== this.placeholder) ? cfgvalue : this.placeholder;
|
||||
picker.style.cssText = 'width:40px;height:32px;padding:2px;border:1px solid #ccc;border-radius:4px;cursor:pointer;vertical-align:middle';
|
||||
picker.addEventListener('input', function() { input.value = picker.value; input.dispatchEvent(new Event('input', { bubbles: true })); });
|
||||
input.addEventListener('input', function() { if (/^#[0-9a-fA-F]{6}$/.test(input.value)) picker.value = input.value; });
|
||||
node.appendChild(picker);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
/* ---- Wallpaper ---- */
|
||||
var BGURL = '/luci-static/noobwrt/background/custom.jpg';
|
||||
var BGTMP = '/tmp/noobwrt_wallpaper.tmp';
|
||||
|
||||
o = s.taboption('wallpaper', form.ListValue, 'online_wallpaper',
|
||||
_('Online Wallpaper Source'),
|
||||
_('Automatically fetch a wallpaper from an online source for the login page background. Set to None to use a custom uploaded image or the built-in default.'));
|
||||
o.value('none', _('None (use custom / default)'));
|
||||
o.value('bing', _('Bing Daily Photo'));
|
||||
o.value('picsum', _('Lorem Picsum (Random)'));
|
||||
o.value('wikimedia', _('Wikimedia Picture of the Day'));
|
||||
o.default = 'none';
|
||||
|
||||
o = s.taboption('wallpaper', form.DummyValue, '_wallpaper_ui', '');
|
||||
o.rawhtml = true;
|
||||
o.default = [
|
||||
'<div style="max-width:520px">',
|
||||
'<p style="color:#888;font-size:13px;margin:0 0 12px">',
|
||||
_('Upload a custom image to use as the login page background. Supported formats: JPG, PNG, WebP (max 10 MB).'),
|
||||
'</p>',
|
||||
'<div id="noobwrt-wp-preview" style="margin-bottom:12px;border-radius:10px;overflow:hidden;background:#f0f0f0;height:160px;display:flex;align-items:center;justify-content:center">',
|
||||
'<img id="noobwrt-wp-img" src="" style="width:100%;height:100%;object-fit:cover;display:none" />',
|
||||
'<span id="noobwrt-wp-placeholder" style="color:#aaa;font-size:13px">' + _('No custom wallpaper — using default') + '</span>',
|
||||
'</div>',
|
||||
'<p id="noobwrt-wp-status" style="font-size:12px;color:#888;min-height:18px;margin:0 0 12px"></p>',
|
||||
'<div style="display:flex;gap:10px">',
|
||||
'<button id="noobwrt-wp-upload" type="button" class="btn cbi-button cbi-button-action">' + _('Upload Wallpaper...') + '</button>',
|
||||
'<button id="noobwrt-wp-revert" type="button" class="btn cbi-button cbi-button-reset">' + _('Revert to Default') + '</button>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
|
||||
/* ---- Background Effects ---- */
|
||||
o = s.taboption('effects', form.Value, 'blur',
|
||||
_('Blur Radius — Light Mode'),
|
||||
_('Login page background blur radius in pixels for light mode. Default: 10'));
|
||||
o.placeholder = '10';
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
o = s.taboption('effects', form.Value, 'blur_dark',
|
||||
_('Blur Radius — Dark Mode'),
|
||||
_('Login page background blur radius in pixels for dark mode. Default: 10'));
|
||||
o.placeholder = '10';
|
||||
o.datatype = 'uinteger';
|
||||
|
||||
o = s.taboption('effects', form.Value, 'transparency',
|
||||
_('Background Opacity — Light Mode'),
|
||||
_('Overlay opacity on the login page background for light mode. Range: 0.0 (transparent) to 1.0 (opaque). Default: 0.8'));
|
||||
o.placeholder = '0.8';
|
||||
|
||||
o = s.taboption('effects', form.Value, 'transparency_dark',
|
||||
_('Background Opacity — Dark Mode'),
|
||||
_('Overlay opacity on the login page background for dark mode. Default: 0.8'));
|
||||
o.placeholder = '0.8';
|
||||
|
||||
/* ---- Toolbar tab – placeholder only, real table rendered below ---- */
|
||||
o = s.taboption('toolbar', form.DummyValue, '_toolbar_placeholder', '');
|
||||
o.rawhtml = true;
|
||||
o.default = '<div id="noobwrt-toolbar-tab-content"></div>';
|
||||
|
||||
/* ---- About ---- */
|
||||
o = s.taboption('about', form.DummyValue, '_about', '');
|
||||
o.rawhtml = true;
|
||||
o.default = [
|
||||
'<div style="max-width:480px;background:var(--color-bg-1,#fff);border:1px solid var(--color-border,#e5e7eb);border-radius:12px;padding:20px 24px;box-shadow:0 2px 8px rgba(0,0,0,0.06)">',
|
||||
' <h3 style="margin:0 0 6px;font-size:16px;font-weight:600">' + _('NoobWrt Theme') + '</h3>',
|
||||
' <p style="margin:0 0 16px;color:#888;font-size:13px">' + _('A clean modern LuCI theme for OpenWrt routers.') + '</p>',
|
||||
' <table style="width:100%;border-collapse:collapse;line-height:1.9;font-size:13px">',
|
||||
' <tr><td style="width:130px;color:#888;padding:3px 0">' + _('Developer') + '</td>',
|
||||
' <td><strong>NoobLk</strong></td></tr>',
|
||||
' <tr><td style="color:#888;padding:3px 0">' + _('Repository') + '</td>',
|
||||
' <td><a href="https://github.com/nooblk-98/luci-theme-noobwrt" target="_blank" rel="noopener" style="color:#5e72e4">github.com/nooblk-98/luci-theme-noobwrt</a></td></tr>',
|
||||
' <tr><td style="color:#888;padding:3px 0">' + _('Contributors') + '</td>',
|
||||
' <td><a href="https://github.com/nooblk-98/luci-theme-noobwrt/graphs/contributors" target="_blank" rel="noopener" style="color:#5e72e4">' + _('View on GitHub') + '</a></td></tr>',
|
||||
' <tr><td style="color:#888;padding:3px 0">' + _('License') + '</td>',
|
||||
' <td>MIT</td></tr>',
|
||||
' <tr><td style="color:#888;padding:3px 0">' + _('Issues / Support') + '</td>',
|
||||
' <td><a href="https://github.com/nooblk-98/luci-theme-noobwrt/issues" target="_blank" rel="noopener" style="color:#5e72e4">' + _('Open an issue') + '</a></td></tr>',
|
||||
' </table>',
|
||||
' <p style="margin:16px 0 0;color:#bbb;font-size:12px;text-align:center;border-top:1px solid var(--color-border,#e5e7eb);padding-top:12px">' + _('Made with \u2665 for the OpenWrt community') + '</p>',
|
||||
'</div>'
|
||||
].join('\n');
|
||||
|
||||
/* ============================================================
|
||||
* SECTION: Toolbar Panel Items (TableSection)
|
||||
* Rendered separately, then moved into the Toolbar tab via JS.
|
||||
* ============================================================ */
|
||||
var ts = m.section(form.TableSection, 'toolbar_item',
|
||||
_('Toolbar Panel Items'),
|
||||
_('Quick-access links displayed in the right-side toolbar panel on every page. Items are rendered in ascending Order number. Use the Add button below to create new entries.'));
|
||||
ts.addremove = true;
|
||||
ts.anonymous = true;
|
||||
ts.sortable = false;
|
||||
ts.nodescriptions = true;
|
||||
|
||||
o = ts.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.default = '1';
|
||||
o.editable = true;
|
||||
|
||||
o = ts.option(form.Value, 'title', _('Label'));
|
||||
o.placeholder = 'Home';
|
||||
o.rmempty = false;
|
||||
o.editable = true;
|
||||
|
||||
o = ts.option(form.Value, 'url', _('URL / Path'));
|
||||
o.placeholder = '/cgi-bin/luci/admin/status/overview';
|
||||
o.rmempty = false;
|
||||
o.editable = true;
|
||||
|
||||
o = ts.option(form.ListValue, 'icon', _('Icon'));
|
||||
[
|
||||
['home.png', _('Home')],
|
||||
['signal.png', _('Signal / Status')],
|
||||
['cell.png', _('Cell / Modem')],
|
||||
['sms.png', _('SMS')],
|
||||
['network.png', _('Network / Data Usage')],
|
||||
['nas.png', _('NAS / Storage')],
|
||||
['wifi.png', _('WiFi / Wireless')],
|
||||
['firewall.png', _('Firewall')],
|
||||
['settings.png', _('Settings / System')],
|
||||
['terminal.png', _('Terminal')],
|
||||
['vpn.png', _('VPN')],
|
||||
['files.png', _('Files')],
|
||||
['info.png', _('Info / Log')]
|
||||
].forEach(function (v) { o.value(v[0], v[1]); });
|
||||
o.editable = true;
|
||||
|
||||
o = ts.option(form.Value, 'order',
|
||||
_('Order'),
|
||||
_('Lower numbers appear first in the toolbar.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = '10';
|
||||
o.editable = true;
|
||||
|
||||
return m.render().then(function (node) {
|
||||
/* ---- Wire wallpaper buttons ---- */
|
||||
var uploadBtn = node.querySelector('#noobwrt-wp-upload');
|
||||
var revertBtn = node.querySelector('#noobwrt-wp-revert');
|
||||
var wpImg = node.querySelector('#noobwrt-wp-img');
|
||||
var wpPh = node.querySelector('#noobwrt-wp-placeholder');
|
||||
var wpSt = node.querySelector('#noobwrt-wp-status');
|
||||
|
||||
function setStatus(msg, ok) {
|
||||
wpSt.textContent = msg;
|
||||
wpSt.style.color = ok === true ? '#2dce89' : ok === false ? '#f5365c' : '#888';
|
||||
}
|
||||
|
||||
fetch(BGURL, { method: 'HEAD' })
|
||||
.then(function (r) {
|
||||
if (r.ok) { wpImg.src = BGURL + '?_=' + Date.now(); wpImg.style.display = ''; wpPh.style.display = 'none'; }
|
||||
}).catch(function () {});
|
||||
|
||||
uploadBtn.addEventListener('click', function (ev) {
|
||||
ui.uploadFile(BGTMP, ev.target)
|
||||
.then(function () { return callSetCustomWallpaper(); })
|
||||
.then(function (result) {
|
||||
if (result === 0) {
|
||||
wpImg.src = BGURL + '?_=' + Date.now(); wpImg.style.display = ''; wpPh.style.display = 'none';
|
||||
setStatus(_('Wallpaper uploaded! Refresh the login page to see it.'), true);
|
||||
} else {
|
||||
setStatus(_('Failed to save wallpaper.'), false);
|
||||
}
|
||||
})
|
||||
.catch(function (e) { setStatus(_('Upload error: ') + e.message, false); });
|
||||
});
|
||||
|
||||
revertBtn.addEventListener('click', function () {
|
||||
if (!window.confirm(_('Remove custom wallpaper and revert to default?'))) return;
|
||||
callRemoveCustomWallpaper()
|
||||
.then(function () {
|
||||
wpImg.src = ''; wpImg.style.display = 'none'; wpPh.style.display = '';
|
||||
setStatus(_('Reverted to default wallpaper.'), true);
|
||||
})
|
||||
.catch(function (e) { setStatus(_('Could not remove: ') + e.message, false); });
|
||||
});
|
||||
|
||||
/* ---- Move TableSection into Toolbar tab ---- */
|
||||
var tabContent = node.querySelector('#noobwrt-toolbar-tab-content');
|
||||
var sections = node.querySelectorAll('.cbi-map > .cbi-section');
|
||||
var tsNode = sections[sections.length - 1];
|
||||
|
||||
if (tabContent && tsNode) {
|
||||
var heading = tsNode.querySelector('h3, .cbi-section-legend');
|
||||
if (heading) heading.style.display = 'none';
|
||||
tabContent.appendChild(tsNode);
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
});
|
||||