op-packages/luci-theme-material3/ucode/template/themes/material3/header.ut
github-actions[bot] a0ef0d598a 💐 Sync 2026-05-31 20:31:25
2026-05-31 20:31:25 +08:00

335 lines
12 KiB
Plaintext

{#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2012 David Menting <david@nut-bolt.nl>
Copyright 2008-2022 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-#}
{%
import { getuid, getspnam } from 'luci.core';
const boardinfo = ubus.call('system', 'board');
http.prepare_content('text/html; charset=UTF-8');
-%}
<!DOCTYPE html>
<html lang="{{ dispatcher.lang }}">
<head>
<meta charset="utf-8">
<title>{{ striptags(`${boardinfo.hostname ?? '?'}${dispatched?.title ? ` | ${_(dispatched.title)}` : ''}`) }}</title>
<script>
(function() {
var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'),
modeKey = 'material3-mode-debug',
themeKey = 'material3-theme-debug',
rootElement = document.documentElement,
themeColors = {
'baseline': '#ADA4BB',
'blue': '#9EA9BA',
'green': '#9DAD9C',
'teal': '#96ADAC',
'orange': '#C0A48B',
'vivid-orange': '#C5A190',
'sakura': '#C4A09B',
'rose': '#C0A0A7',
'gray': '#B4B4B7'
};
var updateFavicon = function() {
var theme = rootElement.getAttribute('data-theme') || 'baseline',
container = themeColors[theme] || themeColors['baseline'],
svg = '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><circle cx="50" cy="50" r="50" fill="' + container + '"/><path fill="#fff" d="M75.26 51.66H24.74a9.18 9.18 0 0 0-9.18 9.18v4.6a9.18 9.18 0 0 0 9.18 9.18h50.52a9.18 9.18 0 0 0 9.18-9.18v-4.6a9.18 9.18 0 0 0-9.18-9.18zM59.18 68.88a5.17 5.17 0 1 1 5.17-5.17 5.17 5.17 0 0 1-5.17 5.17zm13.2 0a5.17 5.17 0 1 1 5.17-5.17 5.17 5.17 0 0 1-5.16 5.17zM29.34 30.72c6.9-6.62 14.06-9.83 21.59-9.83s15.04 3.44 21.9 10.24a3.44 3.44 0 1 0 4.73-4.9C69.6 18.26 60.33 14 50.92 14s-18.33 4.06-26.18 11.78a3.44 3.44 0 0 0 4.6 4.94zm3.29 8.1a3.44 3.44 0 0 0 4.59 5.03c8.77-8.17 18.78-7.97 27.4.37a3.44 3.44 0 1 0 4.8-4.96 26.96 26.96 0 0 0-18.5-8.27 26.75 26.75 0 0 0-18.2 7.83h-.1z"/></svg>',
href = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
document.querySelectorAll('link[rel="icon"]').forEach(function(link) {
if (link.href !== href) {
var newLink = link.cloneNode();
newLink.href = href;
link.parentNode.replaceChild(newLink, link);
}
});
document.querySelectorAll('img.material3-logo').forEach(function(img) {
if (img.src !== href) img.src = href;
});
};
var setDarkMode = function(match) {
var mode = localStorage.getItem(modeKey);
if (mode == 'light' || mode == 'dark') {
rootElement.setAttribute('data-mode', mode);
rootElement.setAttribute('data-darkmode', mode == 'dark' ? 'true' : 'false');
}
else {
rootElement.removeAttribute('data-mode');
rootElement.setAttribute('data-darkmode', match.matches ? 'true' : 'false');
}
};
window.material3ApplyColorMode = setDarkMode;
window.material3UpdateFavicon = updateFavicon;
mediaQuery.addEventListener('change', setDarkMode);
setDarkMode(mediaQuery);
var savedTheme = localStorage.getItem(themeKey);
if (savedTheme) {
rootElement.setAttribute('data-theme', savedTheme);
}
window.addEventListener('load', function() {
updateFavicon();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === 'data-theme') updateFavicon();
});
});
observer.observe(rootElement, { attributes: true, attributeFilter: ['data-theme'] });
});
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="darkreader-lock">
<link rel="stylesheet" href="{{ media }}/cascade.css">
<link rel="stylesheet" media="only screen and (max-device-width: 854px)" href="{{ media }}/mobile.css" />
<link rel="icon" href="{{ media }}/logo.svg" sizes="any">
{% 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) }}"></script>
<script src="{{ resource }}/cbi.js"></script>
<style>
#material3-debug-switcher {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 1200;
font: 12px/1 var(--font-sans);
}
#material3-debug-switcher > button {
width: 42px;
height: 42px;
border-radius: 50%;
border: 1px solid var(--border-color-medium);
background: var(--primary-color-high);
color: var(--on-primary-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, .22);
}
#material3-debug-switcher > button::before {
content: '';
display: block;
width: 20px;
height: 20px;
margin: auto;
background: currentColor;
-webkit-mask: url("{{ media }}/icons/palette.svg") center/contain no-repeat;
mask: url("{{ media }}/icons/palette.svg") center/contain no-repeat;
}
#material3-debug-switcher > div {
position: absolute;
right: 0;
bottom: 50px;
display: none;
grid-template-columns: repeat(3, 30px);
gap: 8px;
padding: 10px;
border: 1px solid var(--border-color-medium);
border-radius: 12px;
background: var(--background-color-high);
box-shadow: 0 4px 18px rgba(0, 0, 0, .22);
}
#material3-debug-switcher[open] > div {
display: grid;
}
#material3-debug-switcher button[data-theme],
#material3-debug-switcher button[data-mode] {
width: 30px;
height: 30px;
border: 1px solid var(--border-color-medium);
border-radius: 50%;
background: var(--debug-color, var(--background-color-low));
color: var(--text-color-high);
box-shadow: inset 0 0 0 3px var(--background-color-high);
}
#material3-debug-switcher button[aria-pressed="true"] {
border-color: var(--primary-color-high);
box-shadow: inset 0 0 0 3px var(--background-color-high), 0 0 0 3px color-mix(in srgb, var(--primary-color-high) 30%, transparent);
}
#material3-debug-switcher button[data-mode] {
position: relative;
font-size: 0;
}
#material3-debug-switcher button[data-mode]::before {
content: '';
position: absolute;
inset: 0;
width: 18px;
height: 18px;
margin: auto;
background: currentColor;
-webkit-mask: var(--debug-mode-icon) center/contain no-repeat;
mask: var(--debug-mode-icon) center/contain no-repeat;
}
#material3-debug-switcher button[data-mode="light"] {
--debug-mode-icon: url("{{ media }}/icons/sun.svg");
}
#material3-debug-switcher button[data-mode="dark"] {
--debug-mode-icon: url("{{ media }}/icons/moon.svg");
}
#material3-debug-switcher button[data-mode="auto"] {
--debug-mode-icon: url("{{ media }}/icons/auto.svg");
}
</style>
<script>
(function() {
var root = document.documentElement,
themeKey = 'material3-theme-debug',
modeKey = 'material3-mode-debug',
savedTheme = localStorage.getItem(themeKey);
if (savedTheme)
root.setAttribute('data-theme', savedTheme);
document.addEventListener('DOMContentLoaded', function() {
if (!window.L || !L.env || L.env.requestpath.join('/') != 'admin/system/system')
return;
var themes = [
['baseline', '#6750A4'],
['blue', '#0061A4'],
['green', '#006D3A'],
['teal', '#006A6A'],
['orange', '#8F4C00'],
['vivid-orange', '#E85D04'],
['sakura', '#BB1614'],
['rose', '#BA1A46'],
['gray', '#3F3F46']
];
var switcher = document.createElement('div'),
toggle = document.createElement('button'),
panel = document.createElement('div');
switcher.id = 'material3-debug-switcher';
toggle.type = 'button';
toggle.title = 'Material3 debug switcher';
toggle.setAttribute('aria-label', 'Material3 debug switcher');
toggle.addEventListener('click', function() {
switcher.toggleAttribute('open');
});
function setPressed() {
var theme = root.getAttribute('data-theme') || 'baseline',
mode = localStorage.getItem(modeKey) || 'auto';
panel.querySelectorAll('[data-theme]').forEach(function(button) {
button.setAttribute('aria-pressed', button.getAttribute('data-theme') == theme ? 'true' : 'false');
});
panel.querySelectorAll('[data-mode]').forEach(function(button) {
button.setAttribute('aria-pressed', button.getAttribute('data-mode') == mode ? 'true' : 'false');
});
}
themes.forEach(function(item) {
var button = document.createElement('button');
button.type = 'button';
button.title = item[0];
button.setAttribute('data-theme', item[0]);
button.style.setProperty('--debug-color', item[1]);
button.addEventListener('click', function() {
root.setAttribute('data-theme', item[0]);
localStorage.setItem(themeKey, item[0]);
setPressed();
});
panel.appendChild(button);
});
['auto', 'light', 'dark'].forEach(function(mode) {
var button = document.createElement('button');
button.type = 'button';
button.title = mode;
button.setAttribute('aria-label', mode);
button.setAttribute('data-mode', mode);
button.addEventListener('click', function() {
if (mode == 'auto')
localStorage.removeItem(modeKey);
else
localStorage.setItem(modeKey, mode);
if (window.material3ApplyColorMode)
window.material3ApplyColorMode(window.matchMedia('(prefers-color-scheme: dark)'));
setPressed();
});
panel.appendChild(button);
});
switcher.append(toggle, panel);
document.body.appendChild(switcher);
setPressed();
});
})();
</script>
</head>
<body class="lang_{{ dispatcher.lang }} {{ entityencode(striptags(node?.title ?? ''), true) }}" data-page="{{ entityencode(join('-', ctx.request_path), true) }}">
{% if (!blank_page): %}
<header>
<button class="menu-btn" type="button" aria-label="{{ _('Toggle navigation menu') }}" aria-expanded="false">
<span></span>
</button>
<a class="brand" href="/">{{ striptags(boardinfo.hostname ?? '?') }}</a>
<div id="indicators" class="pull-right"></div>
</header>
<aside class="sidebar" aria-label="{{ _('Navigation') }}">
<ul class="nav" id="topmenu" style="display:none"></ul>
</aside>
<div class="sidebar-overlay"></div>
<div id="maincontent" class="container">
{% if (getuid() == 0 && getspnam('root')?.pwdp === ''): %}
<div class="alert-message warning">
<h4>{{ _('No password set!') }}</h4>
<p>{{ _('There is no password set on this router. Please configure a root password to protect the web interface.') }}</p>
{% if (dispatcher.lookup("admin/system/admin")): %}
<div class="right"><a class="btn" href="{{ dispatcher.build_url("admin/system/admin") }}">{{ _('Go to password configuration...') }}</a></div>
{% endif %}
</div>
{% endif %}
{% if (boardinfo.rootfs_type == "initramfs"): %}
<div class="alert-message warning">
<h4>{{ _('System running in recovery (initramfs) mode.') }}</h4>
<p>{{ _('No changes to settings will be stored and are lost after rebooting. This mode should only be used to install a firmware upgrade') }}</p>
{% if (dispatcher.lookup("admin/system/flash")): %}
<div class="right"><a class="btn" href="{{ dispatcher.build_url("admin/system/flash") }}">{{ _('Go to firmware upgrade...') }}</a></div>
{% endif %}
</div>
{% endif %}
<noscript>
<div class="alert-message warning">
<h4>{{ _('JavaScript required!') }}</h4>
<p>{{ _('You must enable JavaScript in your browser or LuCI will not work properly.') }}</p>
</div>
</noscript>
<div id="tabmenu" style="display:none"></div>
{% endif %}