mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
🔥 Sync 2026-09-05 21:18:46
This commit is contained in:
+15
-2
@@ -15,7 +15,7 @@ AGENTFLOW_HASH_aarch64:=6d733ebc49b095bf8670a1b655a13bb8ad98c90ced19701b67d5cdd4
|
||||
|
||||
PKG_NAME:=agentflow
|
||||
PKG_VERSION:=0.3.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
AGENTFLOW_URL_FILE:=agentflow-linux-$(AGENTFLOW_ARCH)
|
||||
AGENTFLOW_DOWNLOAD:=$(AGENTFLOW_URL_FILE)-$(AGENTFLOW_HASH_$(ARCH))
|
||||
@@ -52,6 +52,16 @@ define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/agentflow
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/prerm
|
||||
#!/bin/sh
|
||||
if [ "$${IPKG_INSTROOT}" = "" ]; then
|
||||
if [ "$$(readlink /usr/share/linkeasefull/desktop-apps.d/30-agentflow.json 2>/dev/null)" = "/usr/share/agentflow/agentflow-plugin.json" ]; then
|
||||
rm -f /usr/share/linkeasefull/desktop-apps.d/30-agentflow.json
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(INSTALL_DIR) $(PKG_BUILD_DIR)
|
||||
$(CP) $(DL_DIR)/$(AGENTFLOW_DOWNLOAD) $(PKG_BUILD_DIR)/agentflow
|
||||
@@ -64,11 +74,14 @@ define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/uci-defaults $(1)/etc/config
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/uci-defaults $(1)/etc/config $(1)/usr/share/agentflow/www $(1)/usr/share/linkeasefull/desktop-apps.d
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/agentflow $(1)/usr/sbin/agentflow
|
||||
$(INSTALL_BIN) ./files/agentflow.init $(1)/etc/init.d/agentflow
|
||||
$(INSTALL_BIN) ./files/agentflow.uci-default $(1)/etc/uci-defaults/09-agentflow
|
||||
$(INSTALL_CONF) ./files/agentflow.config $(1)/etc/config/agentflow
|
||||
$(INSTALL_DATA) ./files/agentflow-plugin.json $(1)/usr/share/agentflow/agentflow-plugin.json
|
||||
$(INSTALL_DATA) ./files/www/logo.svg $(1)/usr/share/agentflow/www/logo.svg
|
||||
ln -sf /usr/share/agentflow/agentflow-plugin.json $(1)/usr/share/linkeasefull/desktop-apps.d/30-agentflow.json
|
||||
endef
|
||||
|
||||
$(eval $(call Download,agentflow))
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "agentflow",
|
||||
"name": "AgentFlow",
|
||||
"icon": "logo.svg",
|
||||
"staticRoot": "/usr/share/agentflow/www",
|
||||
"desktop": {
|
||||
"mode": "module",
|
||||
"entry": "desktop-entry.js",
|
||||
"isolation": "shadow-dom"
|
||||
},
|
||||
"standalone": {
|
||||
"entry": "index.html",
|
||||
"basePath": "/apps/agentflow/",
|
||||
"url": "/cgi-bin/luci/admin/services/agentflow/open",
|
||||
"externalOpen": {
|
||||
"enabled": true,
|
||||
"label": "Open AgentFlow"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"mode": "passthrough"
|
||||
},
|
||||
"backend": {
|
||||
"type": "http",
|
||||
"scheme": "http",
|
||||
"host": "127.0.0.1",
|
||||
"portFromUci": "agentflow.@agentflow[0].port",
|
||||
"defaultPort": 9000,
|
||||
"upstreamBasePath": "/apps/agentflow/",
|
||||
"pathMode": "preserve",
|
||||
"proxyMode": "app-base"
|
||||
},
|
||||
"window": {
|
||||
"width": 1180,
|
||||
"height": 760,
|
||||
"singleton": true
|
||||
},
|
||||
"capabilities": ["agent", "coding", "workflow"],
|
||||
"categories": ["development", "ai"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<svg
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 150 150"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="150" height="150" rx="30" fill="#f5f1e8" />
|
||||
<text
|
||||
x="50%"
|
||||
y="54%"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
font-size="68"
|
||||
font-weight="700"
|
||||
font-family="Georgia, 'Times New Roman', serif"
|
||||
letter-spacing="-4"
|
||||
fill="#111111"
|
||||
>
|
||||
AF
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
@@ -0,0 +1,48 @@
|
||||
import json
|
||||
import pathlib
|
||||
import unittest
|
||||
|
||||
|
||||
APP_DIR = pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class DesktopManifestContractTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.manifest = json.loads(
|
||||
(APP_DIR / "files" / "agentflow-plugin.json").read_text()
|
||||
)
|
||||
|
||||
def test_manifest_uses_agentflow_app_base_proxy(self):
|
||||
self.assertEqual(self.manifest["id"], "agentflow")
|
||||
self.assertEqual(self.manifest["staticRoot"], "/usr/share/agentflow/www")
|
||||
self.assertEqual(self.manifest["standalone"]["entry"], "index.html")
|
||||
self.assertEqual(self.manifest["standalone"]["basePath"], "/apps/agentflow/")
|
||||
self.assertEqual(
|
||||
self.manifest["standalone"]["url"],
|
||||
"/cgi-bin/luci/admin/services/agentflow/open",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.manifest["standalone"]["externalOpen"],
|
||||
{"enabled": True, "label": "Open AgentFlow"},
|
||||
)
|
||||
self.assertEqual(self.manifest["auth"]["mode"], "passthrough")
|
||||
|
||||
backend = self.manifest["backend"]
|
||||
self.assertEqual(backend["upstreamBasePath"], "/apps/agentflow/")
|
||||
self.assertEqual(backend["pathMode"], "preserve")
|
||||
self.assertEqual(backend["proxyMode"], "app-base")
|
||||
|
||||
def test_desktop_entry_matches_manifest_contract(self):
|
||||
desktop = self.manifest["desktop"]
|
||||
self.assertEqual(desktop["mode"], "module")
|
||||
self.assertEqual(desktop["entry"], "desktop-entry.js")
|
||||
self.assertEqual(desktop["isolation"], "shadow-dom")
|
||||
|
||||
self.assertFalse(
|
||||
(APP_DIR / "files" / "www" / "desktop-entry.js").exists(),
|
||||
"desktop-entry.js must be served by AgentFlow backend, not the static iframe wrapper",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -2,12 +2,27 @@ from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
APPLICATIONS = ROOT / "applications"
|
||||
THIS_FILE = Path(__file__).resolve()
|
||||
|
||||
|
||||
def read(relative):
|
||||
return (APPLICATIONS / relative).read_text()
|
||||
relative_path = Path(relative)
|
||||
package = relative_path.parts[0]
|
||||
app = package[9:] if package.startswith("luci-app-") else package
|
||||
remainder = Path(*relative_path.parts[1:])
|
||||
|
||||
candidates = [
|
||||
THIS_FILE.parents[index] / relative_path
|
||||
for index in range(2, min(len(THIS_FILE.parents), 6))
|
||||
]
|
||||
candidates.append(THIS_FILE.parents[3] / app / package / remainder)
|
||||
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate.read_text()
|
||||
|
||||
searched = "\n".join(str(candidate) for candidate in candidates)
|
||||
raise FileNotFoundError(f"{relative} not found; searched:\n{searched}")
|
||||
|
||||
|
||||
class MiseRuntimeContractTest(unittest.TestCase):
|
||||
|
||||
+49
-40
@@ -1,47 +1,46 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2017-2024
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=filebrowser
|
||||
PKG_VERSION:=1.5.6-stable
|
||||
PKG_RELEASE=1
|
||||
PKG_VERSION:=2.63.23
|
||||
PKG_RELEASE:=21
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
PKG_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
else ifeq ($(ARCH),arm)
|
||||
PKG_ARCH:=armv7
|
||||
PKG_HASH:=skip
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
PKG_ARCH:=amd64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host node/host node-pnpm/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/filebrowser/filebrowser/v2
|
||||
GO_PKG_LDFLAGS_X:= \
|
||||
$(GO_PKG)/version.CommitSHA=$(PKG_VERSION) \
|
||||
$(GO_PKG)/version.Version=v$(PKG_VERSION)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/filebrowser
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
DEPENDS:=@(arm||aarch64||x86_64)
|
||||
TITLE:=FileBrowser Quantum
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Web File Browser
|
||||
URL:=https://github.com/filebrowser/filebrowser
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/filebrowser/description
|
||||
The best free self-hosted web-based file manager.
|
||||
endef
|
||||
|
||||
PKG_SOURCE:=linux-$(PKG_ARCH)-filebrowser
|
||||
PKG_SOURCE_URL:=https://github.com/gtsteffaniak/filebrowser/releases/download/v$(PKG_VERSION)
|
||||
|
||||
define Build/Prepare
|
||||
$(call Build/Prepare/Default)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
filebrowser provides a file managing interface within a specified directory
|
||||
and it can be used to upload, delete, preview, rename and edit your files.
|
||||
It allows the creation of multiple users and each user can have its own directory.
|
||||
It can be used as a standalone app or as a middleware.
|
||||
endef
|
||||
|
||||
define Package/filebrowser/conffiles
|
||||
@@ -49,15 +48,25 @@ define Package/filebrowser/conffiles
|
||||
/etc/config/filebrowser
|
||||
endef
|
||||
|
||||
define Package/filebrowser/install
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/filebrowser.config $(1)/etc/config/filebrowser
|
||||
$(INSTALL_DIR) $(1)/etc/filebrowser
|
||||
$(INSTALL_CONF) $(CURDIR)/files/config.yaml $(1)/etc/filebrowser/config.yaml
|
||||
$(INSTALL_DIR) $(1)/usr/bin/
|
||||
$(INSTALL_BIN) $(DL_DIR)/linux-$(PKG_ARCH)-filebrowser $(1)/usr/bin/filebrowser
|
||||
define Build/Compile
|
||||
( \
|
||||
export PNPM_HOME="$(PKG_BUILD_DIR)/frontend" ; \
|
||||
pushd "$(PKG_BUILD_DIR)/frontend" ; \
|
||||
pnpm install --frozen-lockfile ; \
|
||||
pnpm run build ; \
|
||||
popd ; \
|
||||
$(call GoPackage/Build/Compile) ; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Package/filebrowser/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/filebrowser.config $(1)/etc/config/filebrowser
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,filebrowser))
|
||||
$(eval $(call BuildPackage,filebrowser))
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
server:
|
||||
port: 8989
|
||||
database: "/etc/filebrowser/database.db"
|
||||
sources:
|
||||
- path: "/" # Do not use a root "/" directory or include the "/var" folder
|
||||
config:
|
||||
defaultEnabled: true
|
||||
auth:
|
||||
adminUsername: admin
|
||||
adminPassword: "admin"
|
||||
userDefaults:
|
||||
ui:
|
||||
locale: "cn"
|
||||
@@ -2,4 +2,6 @@ config filebrowser 'config'
|
||||
option enabled '0'
|
||||
option listen_port '8989'
|
||||
option root_path '/'
|
||||
option base_url ''
|
||||
option disable_exec '1'
|
||||
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
CONF="filebrowser"
|
||||
PROG="/usr/bin/filebrowser"
|
||||
CONF_PATH="/etc/filebrowser/config.yaml"
|
||||
PID_FILE="/var/run/filebrowser.pid"
|
||||
DB_PATH="/etc/filebrowser/database.db"
|
||||
|
||||
start() {
|
||||
start_service() {
|
||||
config_load "$CONF"
|
||||
|
||||
local enabled
|
||||
config_get_bool enabled "config" "enabled" "0"
|
||||
[ "$enabled" -eq "1" ] || return 1
|
||||
|
||||
local listen_port root_path base_url
|
||||
mkdir -p "${DB_PATH%/*}"
|
||||
|
||||
local listen_port root_path base_url disable_exec
|
||||
config_get listen_port "config" "listen_port" "8989"
|
||||
config_get root_path "config" "root_path" "/"
|
||||
config_get root_path "config" "root_path" "/mnt/"
|
||||
config_get base_url "config" "base_url"
|
||||
config_get_bool disable_exec "config" "disable_exec" "1"
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG"
|
||||
procd_append_param command --database "$DB_PATH"
|
||||
procd_append_param command --address "[::]"
|
||||
procd_append_param command --port "$listen_port"
|
||||
procd_append_param command --root "$root_path"
|
||||
procd_append_param command --disable-exec="$disable_exec"
|
||||
[ -z "$base_url" ] || procd_append_param command --baseurl "$base_url"
|
||||
if [ ! -e "$DB_PATH" ]; then
|
||||
procd_append_param command --username "admin"
|
||||
procd_append_param command --password "$("$PROG" hash "admin")"
|
||||
fi
|
||||
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="1000000 1000000"
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
|
||||
procd_close_instance
|
||||
|
||||
echo "Starting filebrowser..."
|
||||
sed -e "s/ port:.*/ port: $listen_port/" \
|
||||
-e "s, - path:.*, - path: \"$root_path\"," \
|
||||
-i "$CONF_PATH"
|
||||
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH"
|
||||
}
|
||||
|
||||
stop() {
|
||||
kill -9 `pidof filebrowser | sed "s/$$//g"` 2>/dev/null
|
||||
rm -f "$PID_FILE"
|
||||
echo "filebrowser stopped"
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$CONF"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease-common-bin
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=24
|
||||
PKG_RELEASE:=25
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=469e9a582af646e9d3df7aec4b31fcfda9e4a325
|
||||
PKG_SOURCE_VERSION:=7093bc8b705b7b239b8adde8e4d89928157de456
|
||||
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=27
|
||||
PKG_RELEASE:=28
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=469e9a582af646e9d3df7aec4b31fcfda9e4a325
|
||||
PKG_SOURCE_VERSION:=7093bc8b705b7b239b8adde8e4d89928157de456
|
||||
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
@@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkeasefull
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=3.0.17
|
||||
PKG_RELEASE:=26
|
||||
PKG_RELEASE:=27
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=469e9a582af646e9d3df7aec4b31fcfda9e4a325
|
||||
PKG_SOURCE_VERSION:=7093bc8b705b7b239b8adde8e4d89928157de456
|
||||
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=AgentFlow
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
LUCI_DEPENDS:=+agentflow +luci-compat
|
||||
LUCI_MINIFY_CSS:=0
|
||||
LUCI_MINIFY_JS:=0
|
||||
|
||||
@@ -2,28 +2,128 @@ local http = require "luci.http"
|
||||
|
||||
module("luci.controller.agentflow", package.seeall)
|
||||
|
||||
local APPS_PROXY_PREFIX = "/apps=http://127.0.0.1:19290"
|
||||
local DEFAULT_BASE_PATH = "/apps/agentflow/"
|
||||
local DEFAULT_PORT = 9000
|
||||
|
||||
function index()
|
||||
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
|
||||
local open = entry({"admin", "services", "agentflow", "open"}, call("agentflow_open"))
|
||||
open.leaf = true
|
||||
open.dependent = false
|
||||
open.sysauth = false
|
||||
|
||||
if not nixio.fs.access("/etc/config/agentflow") then
|
||||
return
|
||||
end
|
||||
|
||||
local page = entry({"admin", "services", "agentflow"}, cbi("agentflow"), _("AgentFlow"), 100)
|
||||
page.dependent = true
|
||||
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
|
||||
end
|
||||
|
||||
local function uhttpd_has_apps_proxy_prefix()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local mappings = uci:get_list("uhttpd", "main", "proxy_prefix") or {}
|
||||
|
||||
for _, mapping in ipairs(mappings) do
|
||||
if mapping == APPS_PROXY_PREFIX then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function uhttpd_supports_proxy_prefix()
|
||||
local sys = require "luci.sys"
|
||||
return sys.call("grep -qr 'proxy_prefix' /etc/init.d/uhttpd /lib/functions /usr/share/uhttpd 2>/dev/null") == 0
|
||||
end
|
||||
|
||||
local function uhttpd_apps_proxy_available()
|
||||
return uhttpd_supports_proxy_prefix() and uhttpd_has_apps_proxy_prefix()
|
||||
end
|
||||
|
||||
local function linkeasefull_running()
|
||||
local sys = require "luci.sys"
|
||||
return sys.call("[ -x /etc/init.d/linkeasefull ] && /etc/init.d/linkeasefull running >/dev/null 2>&1") == 0
|
||||
end
|
||||
|
||||
local function normalized_base_path(path)
|
||||
path = path or DEFAULT_BASE_PATH
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
path = "/" .. path
|
||||
end
|
||||
if path:sub(-1) ~= "/" then
|
||||
path = path .. "/"
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
local function authority_host(authority)
|
||||
if not authority or authority == "" then
|
||||
return ""
|
||||
end
|
||||
if authority:sub(1, 1) == "[" then
|
||||
return authority:match("^%[([^%]]+)%]") or ""
|
||||
end
|
||||
return authority:match("^([^:]+)") or authority
|
||||
end
|
||||
|
||||
local function url_authority(host, port)
|
||||
if not host or host == "" then
|
||||
host = "127.0.0.1"
|
||||
end
|
||||
if host:find(":") and host:sub(1, 1) ~= "[" then
|
||||
host = "[" .. host .. "]"
|
||||
end
|
||||
return host .. ":" .. tostring(port)
|
||||
end
|
||||
|
||||
local function request_or_lan_host()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local host = authority_host(http.getenv("HTTP_HOST") or "")
|
||||
if host ~= "" then
|
||||
return host
|
||||
end
|
||||
return uci:get("network", "lan", "ipaddr") or "127.0.0.1"
|
||||
end
|
||||
|
||||
local function agentflow_config()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = tonumber(uci:get_first("agentflow", "agentflow", "port")) or DEFAULT_PORT
|
||||
if port < 1 or port > 65535 then
|
||||
port = DEFAULT_PORT
|
||||
end
|
||||
local base_path = normalized_base_path(uci:get_first("agentflow", "agentflow", "base_path"))
|
||||
return port, base_path
|
||||
end
|
||||
|
||||
local function agentflow_entry_url()
|
||||
local port, base_path = agentflow_config()
|
||||
if linkeasefull_running() and uhttpd_apps_proxy_available() then
|
||||
return base_path
|
||||
end
|
||||
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
|
||||
end
|
||||
|
||||
function agentflow_status()
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local port = tonumber(uci:get_first("agentflow", "agentflow", "port")) or 9000
|
||||
if port < 1 or port > 65535 then
|
||||
port = 9000
|
||||
end
|
||||
local port, base_path = agentflow_config()
|
||||
local entry_url = agentflow_entry_url()
|
||||
|
||||
local status = {
|
||||
running = (sys.call("pidof agentflow >/dev/null") == 0),
|
||||
port = port
|
||||
port = port,
|
||||
base_path = base_path,
|
||||
entry_url = entry_url,
|
||||
proxy_prefix_supported = uhttpd_supports_proxy_prefix(),
|
||||
proxy_prefix_enabled = uhttpd_apps_proxy_available(),
|
||||
linkeasefull_running = linkeasefull_running()
|
||||
}
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(status)
|
||||
end
|
||||
|
||||
function agentflow_open()
|
||||
local entry_url = agentflow_entry_url()
|
||||
http.redirect(entry_url)
|
||||
end
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var agentflowOpenUrl = '<%=url("admin/services/agentflow/open")%>';
|
||||
XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st) {
|
||||
var el = document.getElementById('agentflow_status');
|
||||
if (st && el) {
|
||||
if (!st.running) {
|
||||
el.innerHTML = '<br/><em style="color:red"><%:The AgentFlow service is not running.%></em>';
|
||||
} else {
|
||||
var accessMode = '';
|
||||
if (st.proxy_prefix_enabled && st.linkeasefull_running) {
|
||||
accessMode = '<br/><em><%:Access mode: LinkEase Desktop /apps proxy.%></em>';
|
||||
} else {
|
||||
accessMode = '<br/><em><%:Access mode: external port.%></em>';
|
||||
}
|
||||
el.innerHTML = '<br/><em style="color:green"><%:The AgentFlow service is running.%></em>'
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open AgentFlow%> \" onclick=\"window.open('http://" + window.location.hostname + ":" + st.port + "/')\"/>";
|
||||
+ accessMode
|
||||
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open AgentFlow%> \" onclick=\"window.open('" + agentflowOpenUrl + "')\"/>";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,12 @@ msgstr "AgentFlow 提供用于编排编码智能体和工作流的 Web 管理界
|
||||
msgid "Click to open AgentFlow"
|
||||
msgstr "点击打开 AgentFlow"
|
||||
|
||||
msgid "Access mode: LinkEase Desktop /apps proxy."
|
||||
msgstr "访问模式:LinkEase Desktop /apps 代理。"
|
||||
|
||||
msgid "Access mode: external port."
|
||||
msgstr "访问模式:外部端口。"
|
||||
|
||||
msgid "Collecting data..."
|
||||
msgstr "正在获取数据……"
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
APP_DIR = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class AgentFlowLuciOpenContractTest(unittest.TestCase):
|
||||
def read(self, relative):
|
||||
return (APP_DIR / relative).read_text(encoding="utf-8")
|
||||
|
||||
def test_luci_open_matches_linkease_proxy_pattern(self):
|
||||
makefile = self.read("Makefile")
|
||||
controller = self.read("luasrc/controller/agentflow.lua")
|
||||
status = self.read("luasrc/view/agentflow/status.htm")
|
||||
|
||||
self.assertNotIn("+luci-lib-linkeaseauth", makefile)
|
||||
self.assertIn('entry({"admin", "services", "agentflow", "open"}', controller)
|
||||
self.assertIn("open.sysauth = false", controller)
|
||||
self.assertIn("function agentflow_open()", controller)
|
||||
self.assertIn("uhttpd_apps_proxy_available()", controller)
|
||||
self.assertIn("linkeasefull_running()", controller)
|
||||
self.assertIn("return base_path", controller)
|
||||
self.assertIn("url_authority(request_or_lan_host(), port)", controller)
|
||||
self.assertIn("http.redirect(entry_url)", controller)
|
||||
self.assertNotIn('linkease_auth_url("auth")', controller)
|
||||
self.assertIn('url("admin/services/agentflow/open")', status)
|
||||
self.assertNotIn('window.location.hostname + ":" + st.port', status)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2023 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
+30
-30
@@ -4,11 +4,22 @@
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require view';
|
||||
'require fs';
|
||||
|
||||
const callServiceList = rpc.declare({
|
||||
object: 'service',
|
||||
method: 'list',
|
||||
params: ['name'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
function getServiceStatus() {
|
||||
return L.resolveDefault(callServiceList('filebrowser'), {}).then(function(res) {
|
||||
let isRunning = false;
|
||||
try {
|
||||
isRunning = res['filebrowser']['instances']['instance1']['running'];
|
||||
} catch (e) { }
|
||||
return isRunning;
|
||||
});
|
||||
}
|
||||
|
||||
function renderStatus(isRunning, port) {
|
||||
@@ -26,21 +37,13 @@ function renderStatus(isRunning, port) {
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: async function () {
|
||||
const promises = await Promise.all([
|
||||
L.resolveDefault(fs.stat('/var/run/filebrowser.pid'), null),
|
||||
uci.load('filebrowser')
|
||||
]);
|
||||
const data = {
|
||||
isRunning: promises[0],
|
||||
conf: promises[1]
|
||||
};
|
||||
return data;
|
||||
load() {
|
||||
return uci.load('filebrowser');
|
||||
},
|
||||
|
||||
render(data) {
|
||||
let m, s, o;
|
||||
let webport = (uci.get(data.conf, 'config', 'listen_port') || '8989');
|
||||
let webport = (uci.get(data, 'config', 'listen_port') || '8989');
|
||||
|
||||
m = new form.Map('filebrowser', _('FileBrowser'),
|
||||
_('FileBrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files..') + '<br />'+
|
||||
@@ -49,24 +52,17 @@ return view.extend({
|
||||
s = m.section(form.TypedSection);
|
||||
s.anonymous = true;
|
||||
s.render = function() {
|
||||
poll.add(function() {
|
||||
return fs.stat('/var/run/filebrowser.pid').then(function(stat) {
|
||||
let view = document.getElementById('service_status');
|
||||
if (view) {
|
||||
view.innerHTML = renderStatus(stat, webport);
|
||||
}
|
||||
}).catch(function() {
|
||||
let view = document.getElementById('service_status');
|
||||
if (view) {
|
||||
view.innerHTML = renderStatus(null, webport);
|
||||
}
|
||||
});
|
||||
});
|
||||
poll.add(function() {
|
||||
return L.resolveDefault(getServiceStatus()).then(function(res) {
|
||||
let view = document.getElementById('service_status');
|
||||
view.innerHTML = renderStatus(res, webport);
|
||||
});
|
||||
});
|
||||
|
||||
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
|
||||
E('p', { id: 'service_status' }, _('Collecting data...'))
|
||||
]);
|
||||
};
|
||||
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
|
||||
E('p', { id: 'service_status' }, _('Collecting data...'))
|
||||
]);
|
||||
}
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'filebrowser');
|
||||
|
||||
@@ -80,7 +76,11 @@ return view.extend({
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'root_path', _('Root directory'));
|
||||
o.default = '/';
|
||||
o.default = '/mnt';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Flag, 'disable_exec', _('Disable Command Runner feature'));
|
||||
o.default = o.enabled;
|
||||
o.rmempty = false;
|
||||
|
||||
return m.render();
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -1 +0,0 @@
|
||||
zh_Hans
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"config": "filebrowser",
|
||||
"init": "filebrowser"
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI page for IPK upload
|
||||
LUCI_DEPENDS:=+luci-lib-fs
|
||||
LUCI_DEPENDS:=+luci-compat
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local fs = require "luci.fs"
|
||||
local fs = require "nixio.fs"
|
||||
local http = luci.http
|
||||
|
||||
ful = SimpleForm("upload", translate("Upload"), nil)
|
||||
@@ -28,8 +28,8 @@ dm.template = "filetransfer/other_dvalue"
|
||||
function Download()
|
||||
local sPath, sFile, fd, block
|
||||
sPath = http.formvalue("dlfile")
|
||||
sFile = nixio.fs.basename(sPath)
|
||||
if luci.fs.isdirectory(sPath) then
|
||||
sFile = fs.basename(sPath)
|
||||
if fs.stat(sPath, "type") == "dir" then
|
||||
fd = io.popen('tar -C "%s" -cz .' % {sPath}, "r")
|
||||
sFile = sFile .. ".tar.gz"
|
||||
else
|
||||
@@ -121,7 +121,7 @@ btnrm.render = function(self, section, scope)
|
||||
end
|
||||
|
||||
btnrm.write = function(self, section)
|
||||
local v = luci.fs.unlink("/tmp/upload/" .. luci.fs.basename(inits[section].name))
|
||||
local v = fs.unlink("/tmp/upload/" .. fs.basename(inits[section].name))
|
||||
if v then table.remove(inits, section) end
|
||||
return v
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=59
|
||||
PKG_RELEASE:=60
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
|
||||
@@ -19,14 +19,9 @@
|
||||
* friends) live in those blocks too, beside the fills they must be legible against: a
|
||||
* dark palette has light fills and needs a dark ink, and one global `#fff` failed WCAG AA
|
||||
* on seven of the eight dark-palette fills (down to 1.69:1). */
|
||||
--fs-shadow: 0 1px 3px rgba(15,23,37,.08);
|
||||
/* pop surfaces (menus, dropdown lists, the select picker): one value rather than a
|
||||
* literal per site */
|
||||
--fs-shadow-pop: 0 12px 34px rgba(0,0,0,.30);
|
||||
/* the top bar's edge: a drop below plus a 1px inset seam, both a quiet 25% of the border
|
||||
* colour */
|
||||
--fs-shadow-bar: 0 1px 3px color-mix(in srgb, var(--fs-border) 25%, transparent),
|
||||
inset 0 -1px 0 color-mix(in srgb, var(--fs-border) 25%, transparent);
|
||||
/* ---- two focus rings, and which one a control takes is the contract ----
|
||||
* -soft is a TINT (10-15% of the accent), measuring 1.15-1.29:1 on the surface it lands
|
||||
* on: a glow, not an indicator, where WCAG 1.4.11 wants 3:1. It is legitimate only beside
|
||||
@@ -79,7 +74,9 @@
|
||||
* The role x step matrix is complete on purpose, read or not: a gap is how the drift
|
||||
* starts, since a rule wanting a level that does not exist invents a percentage.
|
||||
* --fs-accent-soft is the one member not here — its strength differs per MODE (10% light,
|
||||
* 15% dark; hicontrast 10/14) — so it sits in the palette blocks. */
|
||||
* 15% dark; hicontrast dark 14%). LIGHT is identical across every palette and lives as one
|
||||
* declaration in 03-palettes.css's shared :root instead; DARK still varies by palette (one
|
||||
* differs by a digit) and stays declared per palette block. */
|
||||
--fs-good-soft: color-mix(in srgb, var(--fs-good) 12%, transparent);
|
||||
--fs-warn-soft: color-mix(in srgb, var(--fs-warn) 12%, transparent);
|
||||
--fs-danger-soft: color-mix(in srgb, var(--fs-danger) 12%, transparent);
|
||||
@@ -103,13 +100,6 @@
|
||||
* instead of mixing its own percentage. A grep finding no `var(--fs-warn-line)` is
|
||||
* expected. */
|
||||
|
||||
/* frosted pop surface (top-nav dropdown, rail flyout), deliberately not merged into
|
||||
* --fs-bar-bg (88%): a bar is a thin strip the page ghosts through on purpose, while a
|
||||
* dropdown carries a whole menu and must stay readable */
|
||||
--fs-glass: color-mix(in srgb, var(--fs-panel) 96%, transparent);
|
||||
/* one blur radius for every frosted surface — the bar said 10px and the pops 12px */
|
||||
--fs-blur: blur(12px);
|
||||
|
||||
/* The circular-arrows refresh glyph, as a mask so its colour follows the palette
|
||||
* (--fs-good active, --fs-dim paused). One copy: the poll pill collapses to it both in the
|
||||
* rail and in the bar's compact cluster. A static glyph on purpose — a spinner makes an
|
||||
@@ -127,10 +117,6 @@
|
||||
* the element and drops the border, the bar keeps the bordered square — but not in size. */
|
||||
--fs-icon-refresh-sz: calc(18px * var(--fs-density-type));
|
||||
|
||||
/* the 1px raised edge on a plate carrying text, plus its text shadow */
|
||||
--fs-emboss: inset 0 1px 0 color-mix(in srgb, var(--fs-panel) 5%, transparent);
|
||||
--fs-text-emboss: 0 1px 1px color-mix(in srgb, var(--fs-panel) 75%, transparent);
|
||||
|
||||
/* Hover cue for a solid control — the one cue that cannot be a colour, the fill it shifts
|
||||
* being whatever the role token says. Per mode, and the DIRECTION FLIPS: `filter`
|
||||
* recolours the text too, and a light-mode solid button is a saturated fill with white
|
||||
@@ -164,21 +150,24 @@
|
||||
--fs-sidebar-w: calc(224px * var(--fs-density-box)); /* the expanded vertical sidebar's column */
|
||||
--fs-rail-w: calc(68px * var(--fs-density-box)); /* ...and the same sidebar collapsed to its icon rail */
|
||||
--fs-content-min: 500px; /* below this a content column stops being readable */
|
||||
/* translucent blurred bar surface: the top-nav bar and the sidebar's phone bar are one
|
||||
* component in two markups, so one token keeps their glass tint identical */
|
||||
--fs-bar-bg: color-mix(in srgb, var(--fs-panel) 88%, transparent);
|
||||
/* the bar's surface: the top-nav bar and the sidebar's phone bar are one component in two
|
||||
* markups, so one token keeps the two identical. OPAQUE on purpose. It was 88% of the panel
|
||||
* under a 12px backdrop blur, and the pair was measured on the page most favourable to it —
|
||||
* a pattern wallpaper at full strength, the bar sitting over scrolled content: dropping the
|
||||
* blur moved 0.04% of the pixels, and what the translucency actually bought was the page
|
||||
* showing through the chrome, which reads as a glitch rather than as glass. */
|
||||
--fs-bar-bg: var(--fs-panel);
|
||||
/* height of that bar, and the scroll-margin every anchor target needs to clear it
|
||||
* (base/10-reset.css): if the two drift, an in-page jump lands under the bar */
|
||||
/* The chrome's own controls — the square buttons (search, appearance, log out) and the poll
|
||||
* pill — ride the bar rather than cutting panel-coloured holes in it: the bar is one surface,
|
||||
* and a control on it is carried by its hairline, not by a second fill. `transparent`, not a
|
||||
* copy of --fs-bar-bg, so the strip's own translucency is not applied twice.
|
||||
* and a control on it is carried by its hairline, not by a second fill. `transparent`, so the
|
||||
* control shows the bar itself rather than a second surface stacked on it.
|
||||
*
|
||||
* This gives up what `.label`'s comment in theme/35-alerts.css leans on — that a coloured
|
||||
* variant sits on --fs-panel2, where the palette guarantees >=4.5:1 by construction. On the
|
||||
* bar the backdrop is now whatever the strip lands on, so the ratio is a MEASURED fact per
|
||||
* palette rather than a guaranteed one; the numbers live in the changelog entry. A palette that
|
||||
* needs the old behaviour sets this back to var(--fs-panel2). */
|
||||
* The backdrop a control lands on is therefore --fs-bar-bg, which is --fs-panel: the same
|
||||
* construction `.label`'s comment in theme/35-alerts.css leans on, so a coloured variant keeps
|
||||
* its >=4.5:1 by the palette's guarantee rather than by measurement. A palette that wants the
|
||||
* old panel2 hole sets this back to var(--fs-panel2). */
|
||||
--fs-bar-ctl-bg: transparent;
|
||||
/* The select's chevron, drawn rather than fetched. It used to be an inline SVG data-URI, and
|
||||
* a URI cannot read var() — the stroke colour had to be written INTO the string, so the
|
||||
@@ -561,7 +550,6 @@
|
||||
:root[data-darkmode="true"] {
|
||||
/* palette colours are in 03-palettes.css; only mode-level, palette-independent
|
||||
* tokens live here */
|
||||
--fs-shadow: 0 1px 2px rgba(0,0,0,.4);
|
||||
--fs-disabled-opacity: .45;
|
||||
/* the hover cue flips direction here (see --fs-hover-lift): a dark-mode solid button is a
|
||||
* light fill with dark ink, so brightening moves it away from both the page and its own
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
* one honest knob --fs-tint-c is the strength, in chroma. Light mode has less headroom and
|
||||
* does clip at the top of the wheel, which costs a few units of strength
|
||||
* but not the direction. On a canvas of pure WHITE the cue is nearly
|
||||
* hue-blind — on bootstrap's #ffffff, 180° and 258° land 1/255 apart —
|
||||
* hue-blind — on bootstrap's #fff, 180° and 258° land 1/255 apart —
|
||||
* which is the price of copying a stock theme's surfaces.
|
||||
* contrast is free `l` is copied through, so AA margins barely move; the mix dragged
|
||||
* lightness toward the anchor and cost one export level 4.61 -> 4.40:1.
|
||||
@@ -84,6 +84,12 @@
|
||||
* rotate it with no second copy to keep in sync. Absent an accent hue, --fs-accent IS the
|
||||
* palette's -base value. */
|
||||
--fs-accent: var(--fs-accent-base);
|
||||
/* a tint OF --fs-accent, mixed FROM it: a restated literal goes stale the moment the accent
|
||||
* is recoloured or the accent-hue axis rotates it. LIGHT strength only — every palette's
|
||||
* light block wanted the identical 10%, so this one declaration replaces five copies; a
|
||||
* dark block still declares its own (15%, hicontrast 14%: 02-tokens.css line ~81) and wins
|
||||
* on specificity. */
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
/* the three status colours, derived from the palette's -base hex for the reason the accent
|
||||
* is: an axis rotating a token the palettes declared directly would need a second copy of
|
||||
* every value to rotate from */
|
||||
@@ -253,7 +259,7 @@
|
||||
:root[data-palette="footstrap"] {
|
||||
/* LIGHT */
|
||||
--fs-bg-base: #f6f8fa;
|
||||
--fs-panel-base: #ffffff;
|
||||
--fs-panel-base: #fff;
|
||||
--fs-panel2-base: #f6f8fa;
|
||||
--fs-border-base: #d0d7de;
|
||||
--fs-text: #1f2328;
|
||||
@@ -264,9 +270,6 @@
|
||||
--fs-dim: #33383d;
|
||||
--fs-faint: #3d4348;
|
||||
--fs-accent-base: #0969da;
|
||||
/* a tint OF --fs-accent, mixed FROM it: a restated literal goes stale the moment the accent
|
||||
* is recoloured or the accent-hue axis rotates it */
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
--fs-track: #eaeef2;
|
||||
--fs-good-base: #197c36;
|
||||
--fs-warn-base: #946300;
|
||||
@@ -311,7 +314,7 @@
|
||||
* white ink, and both uses want >=4.5:1 against white. At 5.22/5.35/5.93/5.80 they are
|
||||
* genuinely above the default palette, which is what the name promises. */
|
||||
--fs-bg-base: #eef1f6;
|
||||
--fs-panel-base: #ffffff;
|
||||
--fs-panel-base: #fff;
|
||||
--fs-panel2-base: #f6f8fb;
|
||||
--fs-border-base: #e4e9f1;
|
||||
--fs-text: #101725;
|
||||
@@ -320,7 +323,6 @@
|
||||
--fs-dim: #272f3d;
|
||||
--fs-faint: #2e3745;
|
||||
--fs-accent-base: #0b6fbd;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
--fs-track: #eaeef4;
|
||||
--fs-good-base: #0f7a52;
|
||||
--fs-warn-base: #8a5a00;
|
||||
@@ -360,9 +362,9 @@
|
||||
* they already know keeps it and still gets the chrome, the client navigation and the axes.
|
||||
*
|
||||
* Bootstrap computes everything from HSL axes; these are those axes evaluated:
|
||||
* --background-color high/medium/low #ffffff / #f9f9f9 / #f5f5f5 (dark #222 / #282828 / #2c2c2c)
|
||||
* --background-color high/medium/low #fff / #f9f9f9 / #f5f5f5 (dark #222 / #282828 / #2c2c2c)
|
||||
* --text-color high/medium #404040 / #808080 (dark #bfbfbf / #7f7f7f)
|
||||
* --border-color high/medium #cccccc / #dddddd (dark #555555 / #444444)
|
||||
* --border-color high/medium #ccc / #ddd (dark #555 / #444)
|
||||
* The canvas and the card are both --background-color-high: bootstrap is flat and lets the
|
||||
* border carry the structure. panel2 is its --background-color-low, the surface it stripes
|
||||
* tables and hovers rows with.
|
||||
@@ -381,10 +383,10 @@
|
||||
* to the chrome, not a colourway. */
|
||||
:root[data-palette="bootstrap"] {
|
||||
/* LIGHT */
|
||||
--fs-bg-base: #ffffff;
|
||||
--fs-panel-base: #ffffff;
|
||||
--fs-bg-base: #fff;
|
||||
--fs-panel-base: #fff;
|
||||
--fs-panel2-base: #f5f5f5;
|
||||
--fs-border-base: #cccccc;
|
||||
--fs-border-base: #ccc;
|
||||
/* The ink is the one place this colourway leaves bootstrap's own values behind, the same
|
||||
* decision the default palette made: its #404040 body and #6a6a6a muted tier measure
|
||||
* 10.4:1 and 5.4:1 on white, and the muted one carries every field title. Matched to the
|
||||
@@ -395,8 +397,7 @@
|
||||
--fs-faint: #424242;
|
||||
/* --primary-color-medium; -high (#1976d2) measured 4.22:1 on --fs-panel2 */
|
||||
--fs-accent-base: #1564c0;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
--fs-track: #eeeeee;
|
||||
--fs-track: #eee;
|
||||
--fs-good-base: #007936; /* --success-color-low */
|
||||
--fs-warn-base: #826706; /* #efbd0b darkened; 1.61:1 as text is not a rounding error */
|
||||
--fs-danger-base: #d00000; /* --error-color-low */
|
||||
@@ -408,14 +409,14 @@
|
||||
}
|
||||
:root[data-palette="bootstrap"][data-darkmode="true"] {
|
||||
/* DARK */
|
||||
--fs-bg-base: #222222;
|
||||
--fs-bg-base: #222;
|
||||
--fs-panel-base: #282828;
|
||||
/* bootstrap's own dark --background-color-low (#2c2c2c) sits 0.016 from --fs-panel, under
|
||||
* the 0.02 the ramp check wants, so an app asking high/-low for a gradation gets one colour
|
||||
* twice; #303030 is 0.031. Its light pair is 0.039 apart, so the same check catches this in
|
||||
* the theme this palette is named after. */
|
||||
--fs-panel2-base: #303030;
|
||||
--fs-border-base: #444444;
|
||||
--fs-border-base: #444;
|
||||
--fs-text: #bfbfbf;
|
||||
/* Bootstrap's dark has no third ink, so the two quiet tiers are this palette's own and AA
|
||||
* decides where the fainter one sits: the export tier's -low follows --fs-faint, and on a
|
||||
@@ -428,16 +429,16 @@
|
||||
--fs-faint: #9a9a9a;
|
||||
--fs-accent-base: #53a4c2;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent);
|
||||
--fs-track: #333333;
|
||||
--fs-track: #333;
|
||||
--fs-good-base: #1aaf7b;
|
||||
--fs-warn-base: #aa9968;
|
||||
--fs-danger-base: #dd807e;
|
||||
/* dark ink: these fills are light, and bootstrap's own --on-*-color in dark is likewise its
|
||||
* --background-color-high. #222222 gives 5.65/5.65/5.66/5.65. */
|
||||
--fs-on-accent: #222222;
|
||||
--fs-on-good: #222222;
|
||||
--fs-on-warn: #222222;
|
||||
--fs-on-danger: #222222;
|
||||
* --background-color-high. #222 gives 5.65/5.65/5.66/5.65. */
|
||||
--fs-on-accent: #222;
|
||||
--fs-on-good: #222;
|
||||
--fs-on-warn: #222;
|
||||
--fs-on-danger: #222;
|
||||
}
|
||||
/* ---- 2020 — the OpenWrt 2020 theme's colourway ----
|
||||
*
|
||||
@@ -457,8 +458,8 @@
|
||||
* reads --fs-panel, so in dark mode the navy is the CANVAS and the chrome sits on it. */
|
||||
:root[data-palette="2020"] {
|
||||
/* LIGHT — white content, the CI cyan darkened until it can be read on it */
|
||||
--fs-bg-base: #ffffff;
|
||||
--fs-panel-base: #ffffff;
|
||||
--fs-bg-base: #fff;
|
||||
--fs-panel-base: #fff;
|
||||
--fs-panel2-base: #eef4f8;
|
||||
--fs-border-base: #c3d2dd;
|
||||
/* navy-tinted neutrals rather than grey: the ink belongs to the same scheme as the accent */
|
||||
@@ -467,11 +468,10 @@
|
||||
--fs-faint: #3d5566;
|
||||
/* #00B5E2 held at hue, taken down until it reads: 5.89:1 on white against its own 2.09:1 */
|
||||
--fs-accent-base: #036c92;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
--fs-track: #e3ebf1;
|
||||
--fs-good-base: #276e2a; /* #5CB85C darkened; 2.28:1 as text is not a rounding error */
|
||||
--fs-warn-base: #8a5a00; /* #CC8800, same move */
|
||||
--fs-danger-base: #c1121f; /* #CC1111 is 5.25:1 already — the one that stays where it was */
|
||||
--fs-warn-base: #8a5a00; /* #C80, same move */
|
||||
--fs-danger-base: #c1121f; /* #C11 is 5.25:1 already — the one that stays where it was */
|
||||
/* ink ON the fills above — white clears AA on all four */
|
||||
--fs-on-accent: #fff;
|
||||
--fs-on-good: #fff;
|
||||
@@ -503,8 +503,8 @@
|
||||
/* ---- forum — the OpenWrt forum's Discourse colourway (opt-in) ----
|
||||
*
|
||||
* Sampled from forum.openwrt.org's own `getComputedStyle(document.documentElement)`, one read
|
||||
* per colour scheme: LIGHT --primary #222 (ink) / --secondary #fff (page) / --tertiary #0088cc
|
||||
* (accent) / --quaternary and --danger both #e45735 / --success #009900 / --highlight #ffff4d;
|
||||
* per colour scheme: LIGHT --primary #222 (ink) / --secondary #fff (page) / --tertiary #08c
|
||||
* (accent) / --quaternary and --danger both #e45735 / --success #090 / --highlight #ffff4d;
|
||||
* DARK --primary #ddd / --secondary #222 / --tertiary #0f82af / --danger #e45735 (unchanged
|
||||
* from light) / --success #1ca551 / --highlight #a87137. Discourse names quaternary and
|
||||
* danger as one colour in light and two in dark (#c14924); footstrap has no fifth semantic
|
||||
@@ -526,20 +526,19 @@
|
||||
* move, still the one furthest from AA after it. */
|
||||
:root[data-palette="forum"] {
|
||||
/* LIGHT — the forum's own page, exactly: white canvas, near-black ink */
|
||||
--fs-bg-base: #ffffff;
|
||||
--fs-panel-base: #ffffff;
|
||||
--fs-bg-base: #fff;
|
||||
--fs-panel-base: #fff;
|
||||
--fs-panel2-base: #f0f0f0;
|
||||
--fs-border-base: #dddddd;
|
||||
--fs-text: #222222;
|
||||
--fs-border-base: #ddd;
|
||||
--fs-text: #222;
|
||||
--fs-dim: #3d3d3d;
|
||||
--fs-faint: #4a4a4a;
|
||||
/* --tertiary #0088cc measures 3.89:1 on white; #0074ad held the hue at 5.12:1 there but
|
||||
/* --tertiary #08c measures 3.89:1 on white; #0074ad held the hue at 5.12:1 there but
|
||||
* only 4.49:1 on --fs-panel2, under the 4.5:1 floor the export tier holds every surface to;
|
||||
* #0073ac holds it at 5.19:1 on white and 4.55:1 on --fs-panel2 */
|
||||
--fs-accent-base: #0073ac;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
|
||||
--fs-track: #eeeeee;
|
||||
/* --success #009900 measured 3.78:1, #008000 5.14:1; --quaternary/--danger #e45735
|
||||
--fs-track: #eee;
|
||||
/* --success #090 measured 3.78:1, #008000 5.14:1; --quaternary/--danger #e45735
|
||||
* measured 3.68:1, #c63c1b 5.17:1; --highlight #ffff4d measured 1.07:1 as text, #717100
|
||||
* 5.17:1 — see the block comment above for why highlight fills warn */
|
||||
--fs-good-base: #008000;
|
||||
@@ -553,11 +552,11 @@
|
||||
}
|
||||
:root[data-palette="forum"][data-darkmode="true"] {
|
||||
/* DARK — the forum's own dark scheme: #222 canvas, #ddd ink, both exactly as sampled */
|
||||
--fs-bg-base: #222222;
|
||||
--fs-bg-base: #222;
|
||||
--fs-panel-base: #262626;
|
||||
--fs-panel2-base: #2e2e2e;
|
||||
--fs-border-base: #3a3a3a;
|
||||
--fs-text: #dddddd;
|
||||
--fs-text: #ddd;
|
||||
--fs-dim: #a3a3a3;
|
||||
--fs-faint: #969696;
|
||||
/* --tertiary #0f82af measured 3.66:1 on --fs-bg and 3.12:1 on --fs-panel2, both under the
|
||||
@@ -565,26 +564,24 @@
|
||||
* 5.86:1 on --fs-bg and 5.00:1 on --fs-panel2 */
|
||||
--fs-accent-base: #13a8e3;
|
||||
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent);
|
||||
--fs-track: #333333;
|
||||
--fs-track: #333;
|
||||
/* --success #1ca551 measured 4.24:1 on --fs-panel2, #1fb65a 5.11:1; --danger #e45735
|
||||
* measured 3.69:1, #eb8066 5.05:1; --highlight #a87137 measured 3.29:1, #c99259 5.00:1 —
|
||||
* same reasoning as the light block for why highlight fills warn */
|
||||
--fs-good-base: #1fb65a;
|
||||
--fs-warn-base: #c99259;
|
||||
--fs-danger-base: #eb8066;
|
||||
/* dark ink: these fills are light, and #222222 (= --fs-bg-base) gives 5.86/5.98/5.86/5.92 */
|
||||
--fs-on-accent: #222222;
|
||||
--fs-on-good: #222222;
|
||||
--fs-on-warn: #222222;
|
||||
--fs-on-danger: #222222;
|
||||
/* dark ink: these fills are light, and #222 (= --fs-bg-base) gives 5.86/5.98/5.86/5.92 */
|
||||
--fs-on-accent: #222;
|
||||
--fs-on-good: #222;
|
||||
--fs-on-warn: #222;
|
||||
--fs-on-danger: #222;
|
||||
/* The masthead, and the one place the forum's navy belongs. rgb(0,43,73) is what
|
||||
forum.openwrt.org paints its header with in its DARK scheme — measured live; its light
|
||||
scheme has a white header (--header_background #ffffff, ink #333333), which is what this
|
||||
scheme has a white header (--header_background #fff, ink #333), which is what this
|
||||
palette's light half already gives, so the navy is declared here and only here.
|
||||
color-mix, not a flat fill: --fs-bar-bg is 88% of the panel over the page on purpose (a
|
||||
bar is a thin strip the page ghosts through, 02-tokens.css), and a flat colour would drop
|
||||
that. Over this palette's #222 canvas the strip lands near #06294a, where the bar's own
|
||||
ink (#dddddd) measures 9.9:1 and its dimmed ink (#a3a3a3) 5.4:1. */
|
||||
--fs-bar-bg: color-mix(in srgb, #002b49 88%, transparent);
|
||||
A flat fill, like --fs-bar-bg everywhere (02-tokens.css): the bar's own ink (#ddd)
|
||||
measures 10.7:1 on it and its dimmed ink (#a3a3a3) 5.8:1. */
|
||||
--fs-bar-bg: #002b49;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,17 +47,8 @@
|
||||
--fs-focus-ring: var(--fs-focus-ring-solo);
|
||||
--fs-focus-ring-invalid: 0 0 0 2px var(--fs-panel), 0 0 0 5px var(--fs-danger);
|
||||
|
||||
/* surfaces that are a near-opaque panel anyway */
|
||||
--fs-glass: var(--fs-panel);
|
||||
--fs-bar-bg: var(--fs-panel);
|
||||
--background-color-medium: var(--fs-panel2);
|
||||
|
||||
/* depth cues: dropped rather than approximated — each is a 1px highlight whose whole
|
||||
* purpose is subtlety, and an opaque one reads as a stray line. */
|
||||
--fs-emboss: none;
|
||||
--fs-text-emboss: none;
|
||||
--fs-shadow-bar: 0 1px 0 var(--fs-border);
|
||||
|
||||
/* the scrim over an uploaded photo: --fs-photo-dim is a percentage this cannot apply, so
|
||||
* take the fixed dialog scrim. Losing it entirely would leave login text over a photo. */
|
||||
--fs-photo-scrim: var(--fs-scrim);
|
||||
|
||||
@@ -177,7 +177,6 @@
|
||||
white-space: nowrap;
|
||||
/* surface: theme/45-misc.css (flat) — absorbed */
|
||||
border-radius: var(--fs-radius-sm);
|
||||
box-shadow: var(--fs-emboss);
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
/* a plate of single-line labels, not prose */
|
||||
@@ -194,10 +193,6 @@
|
||||
/* surface + ink: theme/45-misc.css — absorbed */
|
||||
}
|
||||
|
||||
.ifacebox .ifacebox-head[style] {
|
||||
text-shadow: var(--fs-text-emboss);
|
||||
}
|
||||
|
||||
/* The !important fights luci-mod-network's inline zone-colour `style=`: a raw zone colour is too
|
||||
* bright on a dark panel, so it is re-mixed at .4/.3.
|
||||
*
|
||||
@@ -231,9 +226,7 @@
|
||||
padding: var(--fs-space-0-5);
|
||||
/* surface: theme/45-misc.css (flat) — absorbed */
|
||||
color: var(--fs-text);
|
||||
text-shadow: var(--fs-text-emboss);
|
||||
border-radius: var(--fs-radius-sm);
|
||||
box-shadow: var(--fs-emboss);
|
||||
cursor: default;
|
||||
/* a badge is one line of label — the tight step, like .ifacebox above */
|
||||
line-height: var(--fs-leading-tight);
|
||||
@@ -328,7 +321,6 @@
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
color: var(--fs-text);
|
||||
text-shadow: var(--fs-text-emboss);
|
||||
}
|
||||
|
||||
.zonebadge > em,
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
padding: var(--fs-space-4);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,18 +59,6 @@
|
||||
background-size: var(--fs-pattern-size) auto;
|
||||
}
|
||||
|
||||
/* A CBI form has dozens of .btn/.cbi-button; a backdrop-filter on each is a live
|
||||
* backdrop-sampling layer over the pattern — costly on weak GPUs, and invisible anyway
|
||||
* since every button is already opaque. Blur dropped from buttons entirely; closed
|
||||
* dropdowns keep the frost (few of them, and their body can show the pattern through). */
|
||||
/* var(--fs-blur), not a literal: one radius for every frosted surface (02-tokens.css), and
|
||||
* the literal 6px was also invisible to prefers-reduced-transparency, which nulls the token
|
||||
* (95-a11y-media.css) — the one frost that kept blurring for users who asked for opaque. */
|
||||
:root[data-wallpaper="pattern"] .cbi-dropdown:not([open]) {
|
||||
backdrop-filter: var(--fs-blur);
|
||||
-webkit-backdrop-filter: var(--fs-blur);
|
||||
}
|
||||
|
||||
/* No wash layer behind #view: the pattern shows through the gaps between the cards at
|
||||
* full strength. Every block that carries text paints an opaque --fs-panel of its own,
|
||||
* so nothing has to be dimmed globally. */
|
||||
|
||||
@@ -104,8 +104,6 @@
|
||||
flex-direction: row; flex-wrap: wrap; align-items: center;
|
||||
gap: var(--fs-space-2-5); padding: var(--fs-space-2-5) var(--fs-space-4);
|
||||
background: var(--fs-bar-bg);
|
||||
backdrop-filter: var(--fs-blur);
|
||||
-webkit-backdrop-filter: var(--fs-blur);
|
||||
border-inline-end: 0; border-bottom: var(--fs-hairline);
|
||||
/* the submenu popups anchor to this bar and must escape it */
|
||||
overflow: visible;
|
||||
@@ -159,8 +157,7 @@
|
||||
position: absolute; inset-inline: 0 auto; top: calc(100% + 8px); z-index: var(--fs-z-flyout);
|
||||
width: max-content; min-width: 200px; max-width: calc(100vw - 32px);
|
||||
margin: 0; padding: var(--fs-space-2);
|
||||
background: var(--fs-glass);
|
||||
backdrop-filter: var(--fs-blur); -webkit-backdrop-filter: var(--fs-blur);
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg); box-shadow: var(--fs-shadow-pop);
|
||||
}
|
||||
@@ -323,7 +320,6 @@
|
||||
flex-direction: column; flex-wrap: nowrap; align-items: normal;
|
||||
gap: normal; padding: var(--fs-space-5) var(--fs-space-3-5);
|
||||
background: var(--fs-panel);
|
||||
backdrop-filter: none; -webkit-backdrop-filter: none;
|
||||
border-inline-end: var(--fs-hairline); border-bottom: 0;
|
||||
box-shadow: none;
|
||||
/* `overflow: auto`, not overflow-y alone: the bar sets `overflow: visible` for its
|
||||
@@ -388,7 +384,7 @@
|
||||
width: auto; min-width: 0; max-width: none;
|
||||
flex-direction: column; gap: var(--fs-space-0-5);
|
||||
margin: 0 0 0 var(--fs-space-7); padding: 0;
|
||||
background: none; backdrop-filter: none; -webkit-backdrop-filter: none;
|
||||
background: none;
|
||||
border: 0; border-radius: 0; box-shadow: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
}
|
||||
.cbi-section { padding: var(--fs-card-pad); margin-bottom: var(--fs-card-gap); }
|
||||
/* a bare <legend>; the ones inside a .cbi-section are covered below. The ink is the heading
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
background-color: var(--fs-panel);
|
||||
background-image: linear-gradient(var(--fs-alert-tint, transparent), var(--fs-alert-tint, transparent));
|
||||
color: var(--fs-text);
|
||||
box-shadow: var(--fs-shadow);
|
||||
gap: var(--fs-space-3);
|
||||
}
|
||||
/* ---- status lives in the stripe and the surface; the ring is always neutral ----
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
/* kills a folder-tab baseline gradient (the 1px x 29px background image the strip
|
||||
* conventionally carries) by zeroing its size rather than restating it — base's copy is
|
||||
* absorbed, but any gradient a view or third-party app sets is sized away too */
|
||||
|
||||
@@ -153,7 +153,6 @@
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
margin: 0 0 var(--fs-card-gap);
|
||||
padding: var(--fs-space-4);
|
||||
font-size: var(--fs-type-xl);
|
||||
@@ -176,7 +175,6 @@
|
||||
border: var(--fs-hairline);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 var(--fs-radius-lg) var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow);
|
||||
margin: 0 0 var(--fs-card-gap);
|
||||
padding: 0 var(--fs-space-4) var(--fs-space-4);
|
||||
color: var(--fs-dim);
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
/* align the bar's contents to the .fs-content column, so the brand and the menu line
|
||||
* up with the headings and the cards below */
|
||||
padding: 0 max(20px, calc((100% - var(--fs-content-max)) / 2 + var(--fs-content-pad)));
|
||||
box-shadow: var(--fs-shadow-bar);
|
||||
}
|
||||
|
||||
/* the menu rides the brand's row; its flex:1 opens the gap that pushes the right-hand
|
||||
|
||||
@@ -387,8 +387,10 @@
|
||||
* lives in 03-palettes.css per palette/mode block, because a URI cannot read var(); it
|
||||
* replaced a hardcoded grey that ignored palette and dark mode. */
|
||||
select, select.cbi-input-select, .cbi-value-field select {
|
||||
/* -webkit- only: Firefox has had the unprefixed property since 80. */
|
||||
appearance: none; -webkit-appearance: none;
|
||||
/* unprefixed only: the floor (Chrome 108/Firefox 101/Safari 15.4) clears unprefixed
|
||||
* `appearance` everywhere (Chrome 84, Firefox 80, Safari 15.4 — docs/css.md). -webkit- was
|
||||
* 96 B across these four rules for nothing this sheet still has to run on. */
|
||||
appearance: none;
|
||||
background-color: var(--fs-panel2);
|
||||
background-image: var(--fs-select-chevron);
|
||||
/* right inset from the scale, not a literal 12 — the .cbi-select shell below states the same
|
||||
@@ -506,7 +508,7 @@
|
||||
* one inside the 32px shell. */
|
||||
.cbi-select select,
|
||||
.cbi-select select:focus {
|
||||
appearance: none; -webkit-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
@@ -782,22 +784,20 @@
|
||||
/* RangeSlider (UIRangeSlider) — the one LuCI widget base leaves to native browser rendering.
|
||||
* Token-driven track/thumb. */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
appearance: none;
|
||||
width: 100%; height: 6px; min-height: 0; padding: 0;
|
||||
background: var(--fs-track); border: 0; border-radius: var(--fs-radius-pill);
|
||||
outline: none; box-shadow: none; cursor: pointer;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
appearance: none;
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
background: var(--fs-accent); border: 2px solid var(--fs-panel);
|
||||
box-shadow: var(--fs-shadow); cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
background: var(--fs-accent); border: 2px solid var(--fs-panel); cursor: pointer;
|
||||
/* same shadow the -webkit thumb carries — Firefox had silently drifted flat */
|
||||
box-shadow: var(--fs-shadow);
|
||||
}
|
||||
input[type="range"]::-moz-range-track { background: var(--fs-track); height: 6px; border-radius: var(--fs-radius-pill); }
|
||||
/* -solo: the track sets `border: 0` and `outline: none`, so there is no second channel here
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
.fs-search-box {
|
||||
display: flex; flex-direction: column; min-height: 0;
|
||||
width: 100%; max-width: 560px; max-height: min(70dvh, 560px);
|
||||
background: var(--fs-glass);
|
||||
backdrop-filter: var(--fs-blur); -webkit-backdrop-filter: var(--fs-blur);
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline); border-radius: var(--fs-radius-lg);
|
||||
box-shadow: var(--fs-shadow-pop);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -21,16 +21,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* prefers-reduced-transparency — the user asked the OS for opaque surfaces. Every frosted surface
|
||||
* in this theme is a translucent fill plus a backdrop-filter blur, and both go: the fill becomes
|
||||
* the opaque panel, and the blur — the most expensive paint on a phone GPU, the sticky bar's
|
||||
* backdrop being resampled on every scrolled frame — has nothing left to show through. This also
|
||||
* gives a measured escape hatch on devices where that blur is too costly. */
|
||||
/* prefers-reduced-transparency — the user asked the OS for opaque surfaces. Every surface in the
|
||||
* theme is opaque already: no blur, an opaque bar, opaque pop surfaces. The one thing left is the
|
||||
* wash over an uploaded photo. */
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
:root {
|
||||
--fs-bar-bg: var(--fs-panel);
|
||||
--fs-glass: var(--fs-panel);
|
||||
--fs-blur: none;
|
||||
/* the uploaded background is a translucent wash over a photo — exactly the effect this
|
||||
* user asked the OS to drop. Make the scrim opaque so the canvas is flat, not a photo
|
||||
* ghosting through (16-login-bg.css lays it over the image as one background layer). */
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
--fs-bg: #fff; --fs-panel: #fff; --fs-panel2: #f4f4f4;
|
||||
--fs-text: #000; --fs-dim: #333; --fs-faint: #555;
|
||||
--fs-border: #bbb; --fs-track: #ddd;
|
||||
--fs-shadow: none; --fs-shadow-pop: none; --fs-shadow-bar: none;
|
||||
--fs-emboss: none; --fs-text-emboss: none;
|
||||
--fs-bar-bg: #fff; --fs-glass: #fff; --fs-blur: none;
|
||||
--fs-shadow-pop: none;
|
||||
--fs-bar-bg: #fff;
|
||||
}
|
||||
/* Navigation, the search overlay and the action buttons cannot be used on paper. The
|
||||
* Appearance popover was on this list; it is a PAGE now, and a page prints — an admin
|
||||
|
||||
Reference in New Issue
Block a user