mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 20:22:51 +08:00
🌴 Sync 2026-07-07 04:15:13
This commit is contained in:
1 parent
bda6f7a772
commit
add8ea0d42
33 files changed
+446
-401
No files matched your search
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dnsproxy
|
||||
PKG_VERSION:=0.82.1
|
||||
PKG_RELEASE:=12
|
||||
PKG_VERSION:=0.83.0
|
||||
PKG_RELEASE:=13
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/dnsproxy/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daede
|
||||
PKG_VERSION:=1.14.7
|
||||
PKG_RELEASE:=28
|
||||
PKG_RELEASE:=29
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
|
||||
@@ -677,9 +677,24 @@ return view.extend({
|
||||
};
|
||||
const importDirectNodes = function() {
|
||||
const existing = {};
|
||||
const usedTags = {};
|
||||
(((before || {}).nodes || {}).edges || []).forEach(function(node) {
|
||||
existing[clashConverter.normalizeLink(node.link)] = node;
|
||||
if (node.tag)
|
||||
usedTags[String(node.tag)] = true;
|
||||
});
|
||||
const readableNodeTag = function(item, index) {
|
||||
const base = String(item.name || '').replace(/[\x00-\x1f\x7f]+/g, ' ').replace(/\s+/g, ' ').trim()
|
||||
|| _('Node %d').format(index + 1);
|
||||
let tag = base.slice(0, 96);
|
||||
let suffix = 2;
|
||||
while (usedTags[tag]) {
|
||||
const tail = ' ' + suffix++;
|
||||
tag = base.slice(0, 96 - tail.length) + tail;
|
||||
}
|
||||
usedTags[tag] = true;
|
||||
return tag;
|
||||
};
|
||||
const fresh = items.filter(function(item) {
|
||||
const duplicate = existing[clashConverter.normalizeLink(item.link)];
|
||||
if (duplicate)
|
||||
@@ -689,7 +704,7 @@ return view.extend({
|
||||
const imported = fresh.length
|
||||
? graphQL(endpoint,
|
||||
'mutation ImportNodes($args:[ImportArgument!]!){importNodes(rollbackError:false,args:$args){link error node{id}}}',
|
||||
{ args: fresh.map(function(item, index) { return { link: item.link, tag: airportSync.backendId(airportId) + Date.now().toString(36) + (index + 1) }; }) }, token)
|
||||
{ args: fresh.map(function(item, index) { return { link: item.link, tag: readableNodeTag(item, index) }; }) }, token)
|
||||
: Promise.resolve({ importNodes: [] });
|
||||
|
||||
return imported.then(function(result) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Ycarus (Yannick Chabanois) <contact@openmptcprouter.com> for OpenMPTCProuter
|
||||
* Copyright (C) 2024-2026 Ycarus (Yannick Chabanois) <contact@openmptcprouter.com> for OpenMPTCProuter
|
||||
* This is free software, licensed under the GNU General Public License v3.
|
||||
* See /LICENSE for more information
|
||||
*/
|
||||
@@ -276,30 +276,11 @@ return L.view.extend({
|
||||
//o.value("handover", _("handover"));
|
||||
o.default = "off";
|
||||
|
||||
o = s.option(form.Value, "multipath_weight", _("Weight"), _("Only for *weight schedulers/path managers (if any available)") + '<br />' + _("A weight >100 make it more attractive, a weight <100 make it less attractive. Max 256"));
|
||||
o = s.option(form.Value, "multipath_weight", _("Weight"), _("Only used by *weight schedulers/path managers. Ignored if no weight scheduler is selected.") + '<br />' + _("A weight >100 make it more attractive, a weight <100 make it less attractive. Max 256"));
|
||||
o.datatype = "uinteger";
|
||||
o.rmempty = false;
|
||||
o.default = 100;
|
||||
|
||||
return m.render().then(function(mapEl) {
|
||||
function updateWeightVisibility() {
|
||||
var schedulerEl = document.getElementById('widget.cbid.network.globals.mptcp_scheduler');
|
||||
var val = schedulerEl ? schedulerEl.value : (uci.get('network', 'globals', 'mptcp_scheduler') || '');
|
||||
var show = val.toLowerCase().indexOf('weight') > -1;
|
||||
|
||||
mapEl.querySelectorAll('[data-name="multipath_weight"]').forEach(function(el) {
|
||||
var row = el.closest('.cbi-value') || el;
|
||||
row.style.display = show ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
var schedulerEl = document.getElementById('widget.cbid.network.globals.mptcp_scheduler');
|
||||
if (schedulerEl) {
|
||||
schedulerEl.addEventListener('change', updateWeightVisibility);
|
||||
}
|
||||
updateWeightVisibility();
|
||||
|
||||
return mapEl;
|
||||
});
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.7.1
|
||||
PKG_RELEASE:=173
|
||||
PKG_RELEASE:=174
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -179,9 +179,9 @@ insert_nftset() {
|
||||
printf "%s\n" "$@"
|
||||
else
|
||||
cat
|
||||
fi | tr -s ' \t' '\n' | awk -v s="$suffix" -v n="$nftset_name" -v t="$NFTABLE_NAME" '
|
||||
{
|
||||
gsub(/^[ \t\r]+|[ \t\r]+$/, "");
|
||||
fi | awk -v s="$suffix" -v n="$nftset_name" -v t="$NFTABLE_NAME" '
|
||||
BEGIN {
|
||||
RS = "[ \t\n\r]+"
|
||||
}
|
||||
$0 != "" {
|
||||
if (first == 0) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/samba/smb.conf.template": [ "read" ],
|
||||
"/usr/sbin/smbd": [ "exec" ]
|
||||
"/usr/sbin/smbd -V": [ "exec" ]
|
||||
},
|
||||
"uci": [ "samba4" ]
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ return baseclass.extend({
|
||||
rule.extport,
|
||||
rule.proto,
|
||||
expires_str,
|
||||
rule.descr,
|
||||
'%h'.format(rule.descr),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': L.bind(handleDelRule, this, rule.num),
|
||||
|
||||
@@ -66,7 +66,7 @@ return view.extend({
|
||||
rule.extport,
|
||||
rule.proto,
|
||||
expires_str,
|
||||
rule.descr,
|
||||
'%h'.format(rule.descr),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': L.bind(handleDelRule, this, rule.num),
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
# OpenWrt LuCI web UI address
|
||||
VITE_OPENWRT_HOST=http://192.168.1.1
|
||||
# Optional — run `pnpm setup` once and you usually don't need a .env at all.
|
||||
|
||||
# SSH config for syncing .ut template files to the device on change
|
||||
# Leave SSH_HOST empty to disable ut sync.
|
||||
VITE_OPENWRT_SSH_HOST=root@192.168.1.1
|
||||
#
|
||||
# Path to SSH private key (optional, falls back to ssh-agent or ~/.ssh/config)
|
||||
# VITE_OPENWRT_SSH_KEY=~/.ssh/id_rsa
|
||||
# OpenWrt router address — bare IP/hostname; the web proxy target and the
|
||||
# .ut-sync ssh target both derive from it (ssh key selection etc. belongs
|
||||
# in ~/.ssh/config)
|
||||
# VITE_OPENWRT_HOST=192.168.1.1
|
||||
|
||||
# Development server configuration
|
||||
# Dev server bind / port
|
||||
VITE_DEV_PORT=5173
|
||||
|
||||
# Specify which IP addresses the server should listen on
|
||||
# Set to 0.0.0.0 to listen on all addresses, including LAN and public addresses
|
||||
# Set to 127.0.0.1 to listen only on local machine
|
||||
VITE_DEV_HOST=0.0.0.0
|
||||
# 0.0.0.0 = LAN; 127.0.0.1 = local only
|
||||
VITE_DEV_HOST=0.0.0.0
|
||||
@@ -28,18 +28,16 @@ pnpm install
|
||||
### 2. Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and set your OpenWrt device address
|
||||
# VITE_OPENWRT_HOST=http://192.168.1.1
|
||||
# One-shot wizard: asks for the router IP, generates/installs an SSH key
|
||||
# (one router-password prompt), and writes .env
|
||||
pnpm setup
|
||||
```
|
||||
|
||||
**Environment Variables:**
|
||||
If the router is at the default `192.168.1.1` and passwordless SSH already works, no `.env` is needed at all — every value below has a working default.
|
||||
|
||||
- `VITE_OPENWRT_HOST` - Your OpenWrt LuCI web interface URL (required)
|
||||
- `VITE_OPENWRT_SSH_HOST` - SSH target for `.ut` template sync, e.g. `root@192.168.1.1` (optional)
|
||||
- `VITE_OPENWRT_SSH_KEY` - Path to SSH private key (optional, falls back to ssh-agent or `~/.ssh/config`)
|
||||
**Environment Variables** (all optional):
|
||||
|
||||
- `VITE_OPENWRT_HOST` - bare router address, e.g. `192.168.1.1` (default; `host:port` and full-URL forms also accepted). The web proxy target and the `.ut`-sync SSH target (`root@<hostname>`) both derive from it; anything fancier (a dedicated key, a jump host, a non-standard ssh port) belongs in a `Host` block in `~/.ssh/config`, which ssh picks up automatically.
|
||||
- `VITE_DEV_HOST` - Development server host (code default: `127.0.0.1`, `.env.example` sets `0.0.0.0` for LAN access)
|
||||
- `VITE_DEV_PORT` - Development server port (default: `5173`)
|
||||
|
||||
@@ -94,7 +92,7 @@ Third-party compatibility patches are **not** bundled into `main.css` — they a
|
||||
|
||||
**Adding new styles:**
|
||||
|
||||
- New UI component → create `components/_<name>.css` and add an `@import` line to `main.css`. Each file is its own organizational unit — no `@layer` wrappers needed (any that remain are stripped by PostCSS).
|
||||
- New UI component → create `components/_<name>.css` and add an `@import` line to `main.css`. Each file is its own organizational unit — don't add `@layer` wrappers: theme partials stay unlayered, so they outrank Tailwind's layered base/utilities regardless of specificity.
|
||||
- Compatibility fix for a third-party LuCI app/page → add a new file under `media/patches/` (see below).
|
||||
|
||||
All rules use `@apply` with Tailwind utilities and CSS Nesting — no raw CSS properties.
|
||||
@@ -184,46 +182,23 @@ For LuCI-specific JavaScript development, refer to the official API documentatio
|
||||
|
||||
- **CSS changes**: Trigger full page reload via custom HMR handler
|
||||
- **JS changes**: Trigger full page reload via custom HMR handler
|
||||
- **Template changes** (`.ut` files): Auto-synced to router via SCP and trigger full page reload (requires SSH setup, see below)
|
||||
- **Template changes** (`.ut` files): Auto-synced to router over SSH and trigger full page reload (one-time `pnpm setup` required, see below)
|
||||
|
||||
### Template (`.ut`) Live Sync
|
||||
|
||||
The `.ut` template files are rendered server-side on the OpenWrt device. To see template changes during development, the dev server can automatically sync modified `.ut` files to the router via SCP.
|
||||
The `.ut` template files are rendered server-side on the OpenWrt device, so unlike CSS/JS they can't be served locally — the dev server pushes them to the router instead. Run `pnpm setup` once to configure passwordless SSH; after that it's fully automatic:
|
||||
|
||||
**1. Set up SSH key authentication to your router:**
|
||||
- **On startup**, the whole template directory is pushed (as one tarball over ssh stdin — Dropbear has no SFTP server for scp), so edits made while the dev server was down never leave the router stale.
|
||||
- **On save**, changes are debounced and the directory is pushed again, then the browser reloads.
|
||||
- **On page load**, requests to `/cgi-bin` wait for any in-flight push, so a proxied render never uses a stale template.
|
||||
|
||||
```bash
|
||||
# Generate a key if you don't have one
|
||||
ssh-keygen -t ed25519
|
||||
|
||||
# Copy your public key to the router (OpenWrt uses Dropbear, not OpenSSH)
|
||||
cat ~/.ssh/id_ed25519.pub | ssh root@192.168.1.1 "cat >> /etc/dropbear/authorized_keys"
|
||||
|
||||
# Verify passwordless login works
|
||||
ssh root@192.168.1.1 "echo ok"
|
||||
```
|
||||
|
||||
**2. Add SSH config to `.env`:**
|
||||
|
||||
```bash
|
||||
# SSH target for .ut file sync (user@host)
|
||||
VITE_OPENWRT_SSH_HOST=root@192.168.1.1
|
||||
|
||||
# Optional: path to SSH private key (falls back to ssh-agent or ~/.ssh/config)
|
||||
# VITE_OPENWRT_SSH_KEY=~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
**3. Start `pnpm dev` and edit any `.ut` file** — the dev server will automatically sync it to the router and reload the browser.
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
The dev server checks SSH connectivity on startup and prints actionable errors:
|
||||
**Troubleshooting** — sync errors are printed with the fix:
|
||||
|
||||
- **Host key mismatch** (device was reflashed): Run `ssh-keygen -R <device-ip>`, then restart the dev server
|
||||
- **Authentication failed** (public key not on device): Copy your key with the command above
|
||||
- **Authentication failed** (public key not on device): Run `pnpm setup`
|
||||
- **Connection refused/timed out**: Check that the device is online and SSH is enabled
|
||||
|
||||
If `VITE_OPENWRT_SSH_HOST` is not set, template sync is simply disabled and other dev features work normally.
|
||||
A failed sync is retried on the next `.ut` change; CSS/JS dev features work normally without SSH.
|
||||
|
||||
## Building for Production
|
||||
|
||||
@@ -252,10 +227,9 @@ htdocs/luci-static/
|
||||
**Build Process:**
|
||||
|
||||
1. `pnpm gen:tokens` regenerates `src/media/_tokens.css` from `tokens/` (see [Design Tokens](#design-tokens))
|
||||
2. Vite builds the CSS entry points (`src/media/main.css` and `src/media/login.css`)
|
||||
3. Custom PostCSS plugin removes `@layer` at-rules for OpenWrt compatibility
|
||||
4. Custom Vite plugin (`luci-js-compress`) minifies JS files via Terser
|
||||
5. Static assets copied from `.dev/public/aurora/`
|
||||
2. Vite builds the CSS entry points (`src/media/main.css` and `src/media/login.css`), keeping Tailwind's native `@layer` structure
|
||||
3. Custom Vite plugin (`luci-js-compress`) minifies JS files via Terser
|
||||
4. Static assets copied from `.dev/public/aurora/`
|
||||
|
||||
## Package Compilation
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"setup": "node scripts/setup.js",
|
||||
"build": "npm run clean && npm run gen:tokens && vite build",
|
||||
"clean": "node scripts/clean.js",
|
||||
"gen:tokens": "node scripts/gen-tokens.js",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* One-shot dev environment setup: writes .env and installs an SSH key on the
|
||||
* router so ut-sync works without any manual ssh configuration.
|
||||
*
|
||||
* Usage: pnpm setup (asks for the router IP, then one router password prompt)
|
||||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
|
||||
const SSH_OPTS = [
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"UserKnownHostsFile=/dev/null",
|
||||
];
|
||||
|
||||
function canAuth(host) {
|
||||
return (
|
||||
spawnSync(
|
||||
"ssh",
|
||||
[...SSH_OPTS, "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", host, "echo ok"],
|
||||
{ stdio: "ignore" },
|
||||
).status === 0
|
||||
);
|
||||
}
|
||||
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
const ip =
|
||||
(await rl.question("Router IP [192.168.1.1]: ")).trim() || "192.168.1.1";
|
||||
rl.close();
|
||||
|
||||
const host = `root@${ip}`;
|
||||
const sshDir = join(homedir(), ".ssh");
|
||||
|
||||
let pubKeyPath = ["id_ed25519.pub", "id_rsa.pub", "id_ecdsa.pub"]
|
||||
.map((f) => join(sshDir, f))
|
||||
.find((f) => existsSync(f));
|
||||
|
||||
if (!pubKeyPath) {
|
||||
console.log("No SSH key found — generating ~/.ssh/id_ed25519 ...");
|
||||
const keygen = spawnSync(
|
||||
"ssh-keygen",
|
||||
["-t", "ed25519", "-N", "", "-f", join(sshDir, "id_ed25519")],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
if (keygen.status !== 0) {
|
||||
console.error("✗ ssh-keygen failed.");
|
||||
process.exit(1);
|
||||
}
|
||||
pubKeyPath = join(sshDir, "id_ed25519.pub");
|
||||
}
|
||||
|
||||
if (canAuth(host)) {
|
||||
console.log(`✓ Passwordless SSH to ${host} already works.`);
|
||||
} else {
|
||||
console.log(
|
||||
`Installing ${pubKeyPath} on the router (enter the router password once):`,
|
||||
);
|
||||
const pubKey = readFileSync(pubKeyPath, "utf-8").trim();
|
||||
const install = spawnSync(
|
||||
"ssh",
|
||||
[
|
||||
...SSH_OPTS,
|
||||
host,
|
||||
`mkdir -p /etc/dropbear && echo '${pubKey}' >> /etc/dropbear/authorized_keys && chmod 600 /etc/dropbear/authorized_keys`,
|
||||
],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
if (install.status !== 0 || !canAuth(host)) {
|
||||
console.error(
|
||||
`✗ Key install failed. Check that the device at ${ip} is reachable and the password is correct, then re-run pnpm setup.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✓ Passwordless SSH configured.");
|
||||
}
|
||||
|
||||
const envPath = resolve(process.cwd(), ".env");
|
||||
const kept = existsSync(envPath)
|
||||
? readFileSync(envPath, "utf-8")
|
||||
.split("\n")
|
||||
.filter((l) => l && !l.startsWith("VITE_OPENWRT_HOST="))
|
||||
: [];
|
||||
writeFileSync(
|
||||
envPath,
|
||||
[`VITE_OPENWRT_HOST=${ip}`, ...kept].join("\n") + "\n",
|
||||
);
|
||||
console.log(`✓ Wrote .env (VITE_OPENWRT_HOST=${ip})`);
|
||||
console.log("Done — run `pnpm dev`.");
|
||||
@@ -18,118 +18,116 @@
|
||||
}
|
||||
}
|
||||
|
||||
@layer login {
|
||||
.login-screen {
|
||||
@apply relative flex min-h-dvh flex-col items-center justify-center px-5 pb-14;
|
||||
}
|
||||
.login-screen {
|
||||
@apply relative flex min-h-dvh flex-col items-center justify-center px-5 pb-14;
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
@apply w-full max-w-md;
|
||||
}
|
||||
.login-shell {
|
||||
@apply w-full max-w-md;
|
||||
}
|
||||
|
||||
/* Fixed sibling layer (not form ancestor) on its own GPU surface so input
|
||||
paints don't re-sample the bg image. DOM order keeps the form on top. */
|
||||
/* LQIP shown immediately; falls back to full image when no LQIP stored */
|
||||
.login-bg {
|
||||
@apply pointer-events-none fixed inset-0 transform-[translateZ(0)] bg-cover bg-center bg-no-repeat [background-image:var(--login-bg-lqip,var(--login-bg))];
|
||||
}
|
||||
/* Fixed sibling layer (not form ancestor) on its own GPU surface so input
|
||||
paints don't re-sample the bg image. DOM order keeps the form on top. */
|
||||
/* LQIP shown immediately; falls back to full image when no LQIP stored */
|
||||
.login-bg {
|
||||
@apply pointer-events-none fixed inset-0 transform-[translateZ(0)] bg-cover bg-center bg-no-repeat [background-image:var(--login-bg-lqip,var(--login-bg))];
|
||||
}
|
||||
|
||||
/* Full image fades in on top once loaded (compositor-only opacity change) */
|
||||
.login-bg::after {
|
||||
@apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 [will-change:opacity] content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)];
|
||||
}
|
||||
/* Full image fades in on top once loaded (compositor-only opacity change) */
|
||||
.login-bg::after {
|
||||
@apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 [will-change:opacity] content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)];
|
||||
}
|
||||
|
||||
.login-bg.full-loaded::after {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.login-bg.full-loaded::after {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
@apply bg-surface flex flex-col gap-7 rounded-4xl p-14 shadow-lg max-md:rounded-3xl max-md:p-9;
|
||||
}
|
||||
.login-card {
|
||||
@apply bg-surface flex flex-col gap-7 rounded-4xl p-14 shadow-lg max-md:rounded-3xl max-md:p-9;
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
@apply flex items-center gap-3.5;
|
||||
}
|
||||
.login-brand {
|
||||
@apply flex items-center gap-3.5;
|
||||
}
|
||||
|
||||
.login-divider {
|
||||
@apply via-hairline h-px w-full animate-[divider-in_0.7s_cubic-bezier(0.16,1,0.3,1)_0.1s_both] border-0 bg-linear-to-r from-transparent to-transparent;
|
||||
}
|
||||
.login-divider {
|
||||
@apply via-hairline h-px w-full animate-[divider-in_0.7s_cubic-bezier(0.16,1,0.3,1)_0.1s_both] border-0 bg-linear-to-r from-transparent to-transparent;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
@apply bg-brand-subtle ring-brand-subtle flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl ring-1;
|
||||
.login-logo {
|
||||
@apply bg-brand-subtle ring-brand-subtle flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl ring-1;
|
||||
|
||||
& img {
|
||||
@apply h-6 w-6;
|
||||
}
|
||||
& img {
|
||||
@apply h-6 w-6;
|
||||
}
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
.login-title {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
|
||||
.login-hostname {
|
||||
@apply text-text text-xl font-semibold tracking-tight;
|
||||
}
|
||||
.login-hostname {
|
||||
@apply text-text text-xl font-semibold tracking-tight;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
@apply text-text text-sm;
|
||||
}
|
||||
.login-subtitle {
|
||||
@apply text-text text-sm;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
@apply flex flex-col gap-5;
|
||||
}
|
||||
.login-form {
|
||||
@apply flex flex-col gap-5;
|
||||
}
|
||||
|
||||
.login-fields {
|
||||
@apply flex flex-col gap-4;
|
||||
}
|
||||
.login-fields {
|
||||
@apply flex flex-col gap-4;
|
||||
}
|
||||
|
||||
.login-field {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
.login-field {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
.login-label {
|
||||
@apply text-text text-xs font-semibold tracking-widest uppercase;
|
||||
}
|
||||
.login-label {
|
||||
@apply text-text text-xs font-semibold tracking-widest uppercase;
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
@apply border-hairline bg-surface-overlay dark:bg-surface-sunken relative rounded-2xl border [contain:layout_style];
|
||||
}
|
||||
.input-wrap {
|
||||
@apply border-hairline bg-surface-overlay dark:bg-surface-sunken relative rounded-2xl border [contain:layout_style];
|
||||
}
|
||||
|
||||
/* Focus ring: opacity-only transition runs on compositor thread, not main thread —
|
||||
border-color changes require a repaint (~400 ms INP); opacity changes do not. */
|
||||
.input-wrap::after {
|
||||
@apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 [will-change:opacity] content-[''] [transition:opacity_150ms_ease];
|
||||
}
|
||||
/* Focus ring: opacity-only transition runs on compositor thread, not main thread —
|
||||
border-color changes require a repaint (~400 ms INP); opacity changes do not. */
|
||||
.input-wrap::after {
|
||||
@apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 [will-change:opacity] content-[''] [transition:opacity_150ms_ease];
|
||||
}
|
||||
|
||||
.input-wrap:focus-within::after {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.input-wrap:focus-within::after {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
@apply text-text placeholder:text-text-subtle w-full appearance-none rounded-2xl border-0 bg-transparent px-3 py-2.5 text-base outline-none;
|
||||
}
|
||||
.login-input {
|
||||
@apply text-text placeholder:text-text-subtle w-full appearance-none rounded-2xl border-0 bg-transparent px-3 py-2.5 text-base outline-none;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
@apply bg-danger-surface text-danger border-hairline rounded-2xl border px-3 py-2 text-center text-sm;
|
||||
}
|
||||
.login-error {
|
||||
@apply bg-danger-surface text-danger border-hairline rounded-2xl border px-3 py-2 text-center text-sm;
|
||||
}
|
||||
|
||||
.login-actions {
|
||||
@apply pt-4;
|
||||
}
|
||||
.login-actions {
|
||||
@apply pt-4;
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
@apply bg-brand text-on-brand w-full cursor-pointer rounded-2xl border-0 py-2.5 text-base font-medium hover:opacity-90 active:opacity-80;
|
||||
}
|
||||
.login-submit {
|
||||
@apply bg-brand text-on-brand w-full cursor-pointer rounded-2xl border-0 py-2.5 text-base font-medium hover:opacity-90 active:opacity-80;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
@apply text-text-muted absolute right-0 bottom-5 left-0 px-5 text-center text-xs;
|
||||
.login-footer {
|
||||
@apply text-text-muted absolute right-0 bottom-5 left-0 px-5 text-center text-xs;
|
||||
|
||||
& p {
|
||||
@apply whitespace-normal;
|
||||
}
|
||||
& p {
|
||||
@apply whitespace-normal;
|
||||
}
|
||||
|
||||
& a {
|
||||
@apply text-brand hover:underline;
|
||||
}
|
||||
& a {
|
||||
@apply text-brand hover:underline;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import browserslist from "browserslist";
|
||||
import { exec } from "child_process";
|
||||
import { watch as fsWatch, readdirSync } from "fs";
|
||||
import { existsSync, readdirSync } from "fs";
|
||||
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
||||
import { browserslistToTargets } from "lightningcss";
|
||||
import { basename, dirname, join, relative, resolve } from "path";
|
||||
@@ -59,35 +59,36 @@ function createLuciJsCompressPlugin(): Plugin {
|
||||
},
|
||||
|
||||
async generateBundle() {
|
||||
for (const filePath of jsFiles) {
|
||||
try {
|
||||
const sourceCode = await readFile(filePath, "utf-8");
|
||||
const compressed = await terserMinify(sourceCode, {
|
||||
parse: { bare_returns: true },
|
||||
compress: false,
|
||||
mangle: false,
|
||||
format: { comments: false, beautify: false },
|
||||
});
|
||||
await Promise.all(
|
||||
jsFiles.map(async (filePath) => {
|
||||
try {
|
||||
const sourceCode = await readFile(filePath, "utf-8");
|
||||
const compressed = await terserMinify(sourceCode, {
|
||||
parse: { bare_returns: true },
|
||||
compress: false,
|
||||
mangle: false,
|
||||
format: { comments: false, beautify: false },
|
||||
});
|
||||
|
||||
const relativePath = relative(
|
||||
resolve(CURRENT_DIR, "src/resource"),
|
||||
filePath,
|
||||
).replace(/\\/g, "/");
|
||||
const outputPath = join(outDir, "resources", relativePath);
|
||||
const relativePath = relative(
|
||||
resolve(CURRENT_DIR, "src/resource"),
|
||||
filePath,
|
||||
).replace(/\\/g, "/");
|
||||
const outputPath = join(outDir, "resources", relativePath);
|
||||
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, compressed.code || sourceCode, "utf-8");
|
||||
} catch (error: any) {
|
||||
console.error(`JS compress failed: ${filePath}`, error?.message);
|
||||
}
|
||||
}
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, compressed.code || sourceCode, "utf-8");
|
||||
} catch (error: any) {
|
||||
console.error(`JS compress failed: ${filePath}`, error?.message);
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface RouteConfig {
|
||||
routes: Record<string, string>;
|
||||
shouldRewrite: boolean;
|
||||
hmrMessage: string;
|
||||
}
|
||||
|
||||
@@ -96,18 +97,13 @@ interface ResourceConfig {
|
||||
js: RouteConfig;
|
||||
}
|
||||
|
||||
// On-demand third-party patches: serve each src/media/patches/<page>.css at
|
||||
// On-demand third-party patches: serve src/media/patches/<page>.css at
|
||||
// /luci-static/aurora/patches/<page>.css in dev. Without this, header.ut's patch
|
||||
// <link> falls through to the OpenWrt proxy (404 / stale router asset) and patch
|
||||
// edits don't trigger HMR. Mirrors the build entries derived from the same dir.
|
||||
function patchCssRoutes(): Record<string, string> {
|
||||
const dir = resolve(CURRENT_DIR, "src/media/patches");
|
||||
return Object.fromEntries(
|
||||
readdirSync(dir)
|
||||
.filter((f) => f.endsWith(".css"))
|
||||
.map((f) => [`/luci-static/aurora/patches/${f}`, `/src/media/patches/${f}`]),
|
||||
);
|
||||
}
|
||||
// edits don't trigger HMR. Matched per request so new patch files work without
|
||||
// a dev-server restart.
|
||||
const PATCH_PUBLIC_PREFIX = "/luci-static/aurora/patches/";
|
||||
const PATCH_SRC_DIR = resolve(CURRENT_DIR, "src/media/patches");
|
||||
|
||||
function createLocalServePlugin(): Plugin {
|
||||
const resourceConfig: ResourceConfig = {
|
||||
@@ -115,9 +111,7 @@ function createLocalServePlugin(): Plugin {
|
||||
routes: {
|
||||
"/luci-static/aurora/main.css": "/src/media/main.css",
|
||||
"/luci-static/aurora/login.css": "/src/media/login.css",
|
||||
...patchCssRoutes(),
|
||||
},
|
||||
shouldRewrite: true,
|
||||
hmrMessage: "CSS file changed",
|
||||
},
|
||||
js: {
|
||||
@@ -126,7 +120,6 @@ function createLocalServePlugin(): Plugin {
|
||||
"src/resource/view/aurora/sysauth.js",
|
||||
"/luci-static/resources/menu-aurora.js": "src/resource/menu-aurora.js",
|
||||
},
|
||||
shouldRewrite: false,
|
||||
hmrMessage: "JS file changed",
|
||||
},
|
||||
};
|
||||
@@ -162,6 +155,14 @@ function createLocalServePlugin(): Plugin {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (pathname.startsWith(PATCH_PUBLIC_PREFIX) && pathname.endsWith(".css")) {
|
||||
const file = basename(pathname);
|
||||
if (existsSync(join(PATCH_SRC_DIR, file))) {
|
||||
req.url = `/src/media/patches/${file}` + (search ? `?${search}` : "");
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
const jsPath = resourceConfig.js.routes[pathname];
|
||||
if (jsPath) {
|
||||
try {
|
||||
@@ -184,6 +185,15 @@ function createLocalServePlugin(): Plugin {
|
||||
handleHotUpdate({ file, server }) {
|
||||
const normalizedFile = file.replace(/\\/g, "/");
|
||||
|
||||
if (
|
||||
normalizedFile.startsWith(PATCH_SRC_DIR.replace(/\\/g, "/") + "/") &&
|
||||
normalizedFile.endsWith(".css")
|
||||
) {
|
||||
console.log(`[HMR] Patch CSS changed: ${basename(normalizedFile)}`);
|
||||
server.ws.send({ type: "full-reload", path: "*" });
|
||||
return [];
|
||||
}
|
||||
|
||||
const resources = [
|
||||
{ map: cssHmrMap, config: resourceConfig.css },
|
||||
{ map: jsHmrMap, config: resourceConfig.js },
|
||||
@@ -204,113 +214,116 @@ function createLocalServePlugin(): Plugin {
|
||||
const UT_TEMPLATE_DIR = resolve(PROJECT_ROOT, "ucode/template/themes/aurora");
|
||||
const UT_REMOTE_DIR = "/usr/share/ucode/luci/template/themes/aurora";
|
||||
|
||||
interface ScpConfig {
|
||||
host: string;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
function buildSshArgs(cfg: ScpConfig): string {
|
||||
const args = ["-o StrictHostKeyChecking=no", "-o UserKnownHostsFile=/dev/null"];
|
||||
if (cfg.key) args.push(`-i "${cfg.key}"`);
|
||||
return args.join(" ");
|
||||
}
|
||||
|
||||
// Stream the file over ssh stdin instead of scp: OpenSSH 9+ scp defaults to
|
||||
// the SFTP protocol, which Dropbear on OpenWrt does not ship a server for.
|
||||
function buildScpCommand(localPath: string, remotePath: string, cfg: ScpConfig): string {
|
||||
return `ssh ${buildSshArgs(cfg)} "${cfg.host}" "cat > '${remotePath}'" < "${localPath}"`;
|
||||
}
|
||||
// Key selection is ssh's own job: ssh-agent or a Host block in ~/.ssh/config.
|
||||
const SSH_ARGS = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
||||
|
||||
function parseHost(sshHost: string): string {
|
||||
const atIndex = sshHost.lastIndexOf("@");
|
||||
return atIndex !== -1 ? sshHost.slice(atIndex + 1) : sshHost;
|
||||
}
|
||||
|
||||
async function checkSshConnection(cfg: ScpConfig): Promise<boolean> {
|
||||
const host = parseHost(cfg.host);
|
||||
function reportSshError(err: any, sshHost: string): void {
|
||||
const host = parseHost(sshHost);
|
||||
const stderr = err?.stderr || err?.message || "";
|
||||
|
||||
if (stderr.includes("Host key verification failed") || stderr.includes("REMOTE HOST IDENTIFICATION HAS CHANGED")) {
|
||||
console.error(`\n[UT Sync] SSH host key mismatch for ${host}.`);
|
||||
console.error(`[UT Sync] The device may have been reflashed. Run this to fix:\n`);
|
||||
console.error(` ssh-keygen -R ${host}\n`);
|
||||
} else if (stderr.includes("Permission denied") || stderr.includes("Authentication failed")) {
|
||||
console.error(`\n[UT Sync] SSH authentication failed for ${sshHost}.`);
|
||||
console.error(`[UT Sync] Run \`pnpm setup\` to configure passwordless login.\n`);
|
||||
} else if (stderr.includes("Connection refused") || stderr.includes("Connection timed out") || stderr.includes("No route to host")) {
|
||||
console.error(`\n[UT Sync] Cannot reach ${host}. Check that the device is online and SSH is enabled.\n`);
|
||||
} else {
|
||||
console.error(`\n[UT Sync] SSH connection failed: ${stderr}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSshConnection(sshHost: string): Promise<boolean> {
|
||||
try {
|
||||
await execAsync(`ssh ${buildSshArgs(cfg)} -o ConnectTimeout=5 "${cfg.host}" echo ok`);
|
||||
await execAsync(`ssh ${SSH_ARGS} -o ConnectTimeout=5 "${sshHost}" echo ok`);
|
||||
console.log(`[UT Sync] SSH connection verified.`);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
const stderr = err?.stderr || err?.message || "";
|
||||
|
||||
if (stderr.includes("Host key verification failed") || stderr.includes("REMOTE HOST IDENTIFICATION HAS CHANGED")) {
|
||||
console.error(`\n[UT Sync] SSH host key mismatch for ${host}.`);
|
||||
console.error(`[UT Sync] The device may have been reflashed. Run this to fix:\n`);
|
||||
console.error(` ssh-keygen -R ${host}\n`);
|
||||
console.error(`[UT Sync] Then restart the dev server.\n`);
|
||||
} else if (stderr.includes("Permission denied") || stderr.includes("Authentication failed")) {
|
||||
console.error(`\n[UT Sync] SSH authentication failed for ${cfg.host}.`);
|
||||
console.error(`[UT Sync] Copy your public key to the device:\n`);
|
||||
console.error(` cat ~/.ssh/id_ed25519.pub | ssh ${cfg.host} "cat >> /etc/dropbear/authorized_keys"\n`);
|
||||
} else if (stderr.includes("Connection refused") || stderr.includes("Connection timed out") || stderr.includes("No route to host")) {
|
||||
console.error(`\n[UT Sync] Cannot reach ${host}. Check that the device is online and SSH is enabled.\n`);
|
||||
} else {
|
||||
console.error(`\n[UT Sync] SSH connection failed: ${stderr}\n`);
|
||||
}
|
||||
|
||||
reportSshError(err, sshHost);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createUtSyncPlugin(cfg: ScpConfig): Plugin {
|
||||
let syncing = false;
|
||||
let pendingFile: string | null = null;
|
||||
function createUtSyncPlugin(sshHost: string): Plugin {
|
||||
let dirty = false;
|
||||
let flushing: Promise<void> | null = null;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function syncFile(filename: string, server: any) {
|
||||
if (syncing) {
|
||||
pendingFile = filename;
|
||||
return;
|
||||
// The templates are tiny (3 files), so every sync just pushes the whole
|
||||
// directory in one tarball streamed over ssh stdin — OpenSSH 9+ scp defaults
|
||||
// to the SFTP protocol, which Dropbear on OpenWrt does not ship a server for.
|
||||
const pushAll = () =>
|
||||
execAsync(
|
||||
`tar -C "${UT_TEMPLATE_DIR}" -cf - . | ssh ${SSH_ARGS} "${sshHost}" "mkdir -p '${UT_REMOTE_DIR}' && tar -xf - -C '${UT_REMOTE_DIR}'"`,
|
||||
);
|
||||
|
||||
const flush = (server: any): Promise<void> => {
|
||||
if (!flushing) {
|
||||
flushing = (async () => {
|
||||
while (dirty) {
|
||||
dirty = false;
|
||||
try {
|
||||
await pushAll();
|
||||
console.log(`[UT Sync] Templates synced to ${sshHost}.`);
|
||||
server.ws.send({ type: "full-reload", path: "*" });
|
||||
} catch (err: any) {
|
||||
reportSshError(err, sshHost);
|
||||
break;
|
||||
}
|
||||
}
|
||||
flushing = null;
|
||||
})();
|
||||
}
|
||||
return flushing;
|
||||
};
|
||||
|
||||
syncing = true;
|
||||
pendingFile = null;
|
||||
|
||||
const filePath = join(UT_TEMPLATE_DIR, filename);
|
||||
const remotePath = `${UT_REMOTE_DIR}/${filename}`;
|
||||
const cmd = buildScpCommand(filePath, remotePath, cfg);
|
||||
|
||||
console.log(`[UT Sync] Syncing ${filename} → ${cfg.host}:${remotePath}`);
|
||||
execAsync(cmd)
|
||||
.then(() => {
|
||||
console.log(`[UT Sync] Done. Reloading browser.`);
|
||||
server.ws.send({ type: "full-reload", path: "*" });
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(`[UT Sync] Failed to sync ${filename}:`, err?.message);
|
||||
})
|
||||
.finally(() => {
|
||||
syncing = false;
|
||||
if (pendingFile) syncFile(pendingFile, server);
|
||||
});
|
||||
}
|
||||
const markDirty = (server: any) => {
|
||||
dirty = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => flush(server), 150);
|
||||
};
|
||||
|
||||
return {
|
||||
name: "ut-sync-plugin",
|
||||
apply: "serve",
|
||||
|
||||
configureServer(server) {
|
||||
if (!cfg.host) {
|
||||
console.log("[UT Sync] Disabled: VITE_OPENWRT_SSH_HOST not set in .env");
|
||||
return;
|
||||
}
|
||||
|
||||
const authInfo = cfg.key ? `key (${cfg.key})` : "ssh-agent/config";
|
||||
console.log(`[UT Sync] Watching ${UT_TEMPLATE_DIR}`);
|
||||
console.log(`[UT Sync] Target: ${cfg.host}:${UT_REMOTE_DIR} (auth: ${authInfo})`);
|
||||
console.log(`[UT Sync] Target: ${sshHost}:${UT_REMOTE_DIR}`);
|
||||
|
||||
checkSshConnection(cfg).then((ok) => {
|
||||
if (!ok) return;
|
||||
// Full push on startup so edits made while the server was down (or a
|
||||
// freshly flashed device) can't leave the router stale.
|
||||
checkSshConnection(sshHost).then((ok) => {
|
||||
if (ok) markDirty(server);
|
||||
});
|
||||
|
||||
// Accept both "change" and "rename" — VS Code atomic saves emit "rename" on macOS
|
||||
const watcher = fsWatch(UT_TEMPLATE_DIR, (eventType, filename) => {
|
||||
if (!filename?.endsWith(".ut")) return;
|
||||
syncFile(filename, server);
|
||||
});
|
||||
server.watcher.add(UT_TEMPLATE_DIR);
|
||||
const onTemplateEvent = (file: string) => {
|
||||
if (file.startsWith(UT_TEMPLATE_DIR) && file.endsWith(".ut")) {
|
||||
markDirty(server);
|
||||
}
|
||||
};
|
||||
server.watcher.on("add", onTemplateEvent);
|
||||
server.watcher.on("change", onTemplateEvent);
|
||||
|
||||
server.httpServer?.on("close", () => watcher.close());
|
||||
// Hold page loads until pending template pushes land, so a proxied
|
||||
// render never uses a stale template.
|
||||
server.middlewares.use((req, res, next) => {
|
||||
if (!req.url?.startsWith("/cgi-bin") || (!dirty && !flushing)) {
|
||||
return next();
|
||||
}
|
||||
if (timer) clearTimeout(timer);
|
||||
flush(server).then(
|
||||
() => next(),
|
||||
() => next(),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -336,23 +349,36 @@ function createRedirectPlugin(): Plugin {
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, CURRENT_DIR, "");
|
||||
const OPENWRT_HOST = env.VITE_OPENWRT_HOST || "http://192.168.1.1:80";
|
||||
const OPENWRT_SSH_HOST = env.VITE_OPENWRT_SSH_HOST || "";
|
||||
const OPENWRT_SSH_KEY = env.VITE_OPENWRT_SSH_KEY || "";
|
||||
// VITE_OPENWRT_HOST is just the router address — a bare IP/hostname like
|
||||
// 192.168.1.1 (host:port and http:// URL forms also work). The web proxy
|
||||
// target and the .ut-sync ssh target are both derived from it; ssh key
|
||||
// selection etc. belongs in ~/.ssh/config, not here.
|
||||
const OPENWRT_RAW = env.VITE_OPENWRT_HOST || "192.168.1.1";
|
||||
const OPENWRT = new URL(
|
||||
/^https?:\/\//.test(OPENWRT_RAW) ? OPENWRT_RAW : `http://${OPENWRT_RAW}`,
|
||||
);
|
||||
const OPENWRT_URL = OPENWRT.origin;
|
||||
const OPENWRT_SSH_HOST = `root@${OPENWRT.hostname}`;
|
||||
const DEV_HOST = env.VITE_DEV_HOST || "127.0.0.1";
|
||||
const DEV_PORT = Number(env.VITE_DEV_PORT) || 5173;
|
||||
|
||||
const proxyConfig = {
|
||||
"/luci-static": {
|
||||
target: OPENWRT_HOST,
|
||||
target: OPENWRT_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
"/cgi-bin": {
|
||||
target: OPENWRT_HOST,
|
||||
target: OPENWRT_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
configure: (proxy: any) => {
|
||||
// Force an uncompressed upstream response: the HTML injection below
|
||||
// treats the body as UTF-8 text and would corrupt a gzipped payload.
|
||||
proxy.on("proxyReq", (proxyReq: any) => {
|
||||
proxyReq.removeHeader("accept-encoding");
|
||||
});
|
||||
|
||||
proxy.on("proxyRes", (proxyRes: any, req: any, res: any) => {
|
||||
const contentType = proxyRes.headers["content-type"] || "";
|
||||
|
||||
@@ -395,7 +421,7 @@ export default defineConfig(({ mode }) => {
|
||||
tailwindcss(),
|
||||
createRedirectPlugin(),
|
||||
createLocalServePlugin(),
|
||||
createUtSyncPlugin({ host: OPENWRT_SSH_HOST, key: OPENWRT_SSH_KEY }),
|
||||
createUtSyncPlugin(OPENWRT_SSH_HOST),
|
||||
createLuciJsCompressPlugin(),
|
||||
],
|
||||
|
||||
@@ -405,22 +431,6 @@ export default defineConfig(({ mode }) => {
|
||||
browserslist("last 4 versions, Firefox ESR, not dead"),
|
||||
),
|
||||
},
|
||||
postcss: {
|
||||
plugins: [
|
||||
{
|
||||
postcssPlugin: "remove-layers",
|
||||
Once(root) {
|
||||
function removeLayers(node: any) {
|
||||
node.walkAtRules("layer", (rule: any) => {
|
||||
removeLayers(rule);
|
||||
rule.replaceWith(rule.nodes);
|
||||
});
|
||||
}
|
||||
removeLayers(root);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
build: {
|
||||
@@ -435,11 +445,11 @@ export default defineConfig(({ mode }) => {
|
||||
// aurora/patches/<page>.css (the `patches/` key prefix lands them there
|
||||
// via assetFileNames below). header.ut links the matching one per page.
|
||||
...Object.fromEntries(
|
||||
readdirSync(resolve(CURRENT_DIR, "src/media/patches"))
|
||||
readdirSync(PATCH_SRC_DIR)
|
||||
.filter((f) => f.endsWith(".css"))
|
||||
.map((f) => [
|
||||
`patches/${f.slice(0, -4)}`,
|
||||
resolve(CURRENT_DIR, "src/media/patches", f),
|
||||
join(PATCH_SRC_DIR, f),
|
||||
]),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ All dev commands run from `.dev/`:
|
||||
|
||||
```bash
|
||||
cd .dev/
|
||||
pnpm setup # One-shot dev setup: router IP → .env, installs SSH key on device
|
||||
pnpm dev # Start Vite dev server (proxies to OpenWrt device)
|
||||
pnpm build # Clean + regenerate tokens + build production assets to htdocs/luci-static/
|
||||
pnpm gen:tokens # Regenerate src/media/_tokens.css from tokens/ (also runs as part of build)
|
||||
@@ -26,7 +27,7 @@ No linter CLI. Formatting uses Prettier with format-on-save (`.vscode/settings.j
|
||||
- `.dev/public/aurora/` → `htdocs/luci-static/aurora/` (copied as-is)
|
||||
- `ucode/template/themes/aurora/*.ut` — server-side templates, not processed by Vite
|
||||
|
||||
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`) and `login.css` (standalone login page; imports `_base.css`). Third-party compatibility patches are not bundled into `main.css`; each lives in `media/patches/<data-page>.css`, builds to its own `htdocs/luci-static/aurora/patches/<data-page>.css`, and is linked on demand by `header.ut`, which discovers installed patches at render time via `fs.lsdir()` and matches them on path-segment-boundary prefixes (dynamic subpages inherit their prefix's patch; third-party packages may drop their own patch into the directory) — see "On-Demand Third-Party Patches" in `.dev/docs/DEVELOPMENT.md`. All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. `@layer` at-rules stripped by PostCSS plugin for OpenWrt compatibility. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
|
||||
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`) and `login.css` (standalone login page; imports `_base.css`). Third-party compatibility patches are not bundled into `main.css`; each lives in `media/patches/<data-page>.css`, builds to its own `htdocs/luci-static/aurora/patches/<data-page>.css`, and is linked on demand by `header.ut`, which discovers installed patches at render time via `fs.lsdir()` and matches them on path-segment-boundary prefixes (dynamic subpages inherit their prefix's patch; third-party packages may drop their own patch into the directory) — see "On-Demand Third-Party Patches" in `.dev/docs/DEVELOPMENT.md`. All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. Built CSS keeps Tailwind's native `@layer` structure (theme partials are unlayered, so they outrank layered utilities); the OKLCH requirement already gates browsers to ones with `@layer` support. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
|
||||
|
||||
**Design tokens**: `_tokens.css` is GENERATED — do not hand-edit (see its header comment). Source of truth is `.dev/tokens/`: `defaults.js` holds the 10 editable input colors per mode (`bg`, `surface`, `text`, `brand`, `on_brand`, `link`, `info`, `warning`, `success`, `danger`); `spec.js` declares how every other token derives from those via `mix`/`shade`/`set`/`alpha`/`const` operators plus `FIXED` literals (shadows); `engine.js`/`resolve.js` do the OKLCH math and flatten everything to plain `oklch(...)` values. After changing inputs or derivations, run `pnpm gen:tokens` to rewrite `_tokens.css`, and `pnpm test` to check the color-math and token invariants.
|
||||
|
||||
@@ -39,5 +40,5 @@ No linter CLI. Formatting uses Prettier with format-on-save (`.vscode/settings.j
|
||||
## Key References
|
||||
|
||||
- **Development guide**: `.dev/docs/DEVELOPMENT.md` — dev server setup, env config, proxy details, CI workflows, directory structure
|
||||
- **Vite config**: `.dev/vite.config.ts` — custom plugins (luci-js-compress, local-serve, redirect, ut-sync, remove-layers)
|
||||
- **Vite config**: `.dev/vite.config.ts` — custom plugins (luci-js-compress, local-serve, redirect, ut-sync)
|
||||
- **Version**: `PKG_VERSION` and `PKG_RELEASE` in `Makefile`
|
||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=1.0.7
|
||||
PKG_RELEASE:=42
|
||||
PKG_VERSION:=1.0.8
|
||||
PKG_RELEASE:=43
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(selecLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-tracking:initial}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;backgrLine truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:calc(var(--radius-base)*.25);--radius-md:calc(var(--radius-base)*.75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base)*1.5);--radius-2xl:calc(var(--radius-base)*2);--radius-3xl:calc(var(--radius-base)*3);--radius-4xl:calc(var(--radius-base)*4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base)*.5);--container-max-width:var(--container-max-width)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);fonLine truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg.tr{border-radius:0}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:var(--hairline);--tw-shadow:var(--app-shadow-lg);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg span:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg td:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg th:where([data-darkmode=true],[data-darkmode=true] *){color:var(--text)!important}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg img[src*=\.svg]:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*2)}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*1)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .th{padding-block:calc(var(--spacing)*2)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist){margin-bottom:calc(var(--spacing)*0);border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-width:0;border-radius:0}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr:hover{background-color:#0000}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*0);border-width:0;display:table-row}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*.5);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{display:table-cell}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td:first-child{padding-right:calc(var(--spacing)*2);white-space:nowrap;opacity:.7}[data-page=admin-dashboard] .Dashboard>.section-content .title h3{border-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*4);color:var(--text);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .title h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}}[data-page=admin-dashboard] .Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td:nth-child(3){top:calc(var(--spacing)*0);width:100%;padding-inline:cLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg.tr{border-radius:0}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:var(--hairline);--tw-shadow:var(--app-shadow-lg);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg span:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg td:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg th:where([data-darkmode=true],[data-darkmode=true] *){color:var(--text)!important}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg img[src*=\.svg]:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*2)}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*1)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .th{padding-block:calc(var(--spacing)*2)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist){margin-bottom:calc(var(--spacing)*0);border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-width:0;border-radius:0}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr:hover{background-color:#0000}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*0);border-width:0;display:table-row}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*.5);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{display:table-cell}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td:first-child{padding-right:calc(var(--spacing)*2);white-space:nowrap;opacity:.7}[data-page=admin-dashboard] .Dashboard>.section-content .title h3{border-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*4);color:var(--text);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .title h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}}[data-page=admin-dashboard] .Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td:nth-child(3){top:calc(var(--spacing)*0);width:10Line truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}[data-page=admin-statistics-graphs] [data-plugin] img:where([data-darkmode=true],[data-darkmode=true] *){--tw-hue-rotate:hue-rotate(150deg);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,);--tw-invert:invert(100%)}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}[data-page=admin-statistics-graphs] [data-plugin] img:where([data-darkmode=true],[data-darkmode=true] *){--tw-hue-rotate:hue-rotate(150deg);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,);--tw-invert:invert(100%)}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
||||
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid}}[data-page=admin-system-filemanager] #file-manager-container{overflow:auto}[data-page=admin-system-filemanager] #file-manager-container #status-bar{border-style:var(--tw-border-style);background-color:var(--surface);border-width:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid}}}[data-page=admin-system-filemanager] #file-manager-container{overflow:auto}[data-page=admin-system-filemanager] #file-manager-container #status-bar{border-style:var(--tw-border-style);background-color:var(--surface);border-width:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}
|
||||
@@ -2,20 +2,28 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=mwan3-ledhelper
|
||||
PKG_VERSION:=0.0.2
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
PKGARCH:=all
|
||||
|
||||
define Package/$(PKG_NAME)/Default
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=WAN
|
||||
TITLE:=MWAN3 Led Helper
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
$(call Package/$(PKG_NAME)/Default)
|
||||
DEPENDS:=+mwan3
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)-nft
|
||||
PKGARCH:=all
|
||||
TITLE:=MWAN3 Led Helper (nft variant)
|
||||
$(call Package/$(PKG_NAME)/Default)
|
||||
TITLE+= (nft variant)
|
||||
DEPENDS:=+mwan3-nft
|
||||
endef
|
||||
|
||||
@@ -23,6 +31,10 @@ define Package/$(PKG_NAME)/description
|
||||
Hotplug script to indicate led for mwan3 link states
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)-nft/description
|
||||
Hotplug script to indicate led for mwan3 link states (nftables/fw4 variant).
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
endef
|
||||
|
||||
@@ -41,6 +53,10 @@ define Package/$(PKG_NAME)-nft/install
|
||||
$(CP) ./root/* $(1)/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)-nft))
|
||||
ifneq ($(BUILD_NFT_ONLY),1)
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
endif
|
||||
ifneq ($(BUILD_IPT_ONLY),1)
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)-nft))
|
||||
endif
|
||||
|
||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=36
|
||||
PKG_RELEASE:=37
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=756fea6d1e541e3d693dea4f82969585ad9262cc
|
||||
PKG_SOURCE_VERSION:=5430b3312cd783ba2179a24297979252c0463bb9
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=xmm-modem
|
||||
PKG_VERSION:=0.1.6
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=0.1.7
|
||||
PKG_RELEASE:=2
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
opengt
|
||||
set com 115200n81
|
||||
set comecho off
|
||||
set senddelay 0.02
|
||||
waitquiet 0.2 0.2
|
||||
|
||||
send "AT+CGAUTH="
|
||||
send $env("CID")
|
||||
send ","
|
||||
send $env("AUTH")
|
||||
send ",\""
|
||||
send $env("USER")
|
||||
send "\",\""
|
||||
send $env("PASS")
|
||||
send "\"^m"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
opengt
|
||||
set com 115200n81
|
||||
set comecho off
|
||||
set senddelay 0.02
|
||||
waitquiet 0.2 0.2
|
||||
|
||||
send "AT+CGPADDR="
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
get 1 "" $s
|
||||
print $s
|
||||
|
||||
send "AT+GTDNS="
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
get 1 "" $s
|
||||
print $s
|
||||
@@ -1,28 +0,0 @@
|
||||
opengt
|
||||
set com 115200n81
|
||||
set comecho off
|
||||
set senddelay 0.02
|
||||
waitquiet 0.2 0.2
|
||||
|
||||
send "AT+CMEE=2^m"
|
||||
send "AT+COPS=0^m"
|
||||
send "AT+CGPIAF=1,0,0,0^m"
|
||||
send "AT+CGDCONT="
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
send "AT+CGDCONT="
|
||||
send $env("CID")
|
||||
send ",\""
|
||||
send $env("PDP")
|
||||
send "\",\""
|
||||
send $env("APN")
|
||||
send "\"^m"
|
||||
send "AT+CGACT=1,"
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
send "AT+CGPADDR="
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
|
||||
get 1 "" $s
|
||||
print $s
|
||||
@@ -4,7 +4,9 @@ opengt
|
||||
set senddelay 0.02
|
||||
waitquiet 0.2 0.2
|
||||
|
||||
send "AT+XGAUTH="
|
||||
send "AT+"
|
||||
send $env("AUTHCMD")
|
||||
send "="
|
||||
send $env("CID")
|
||||
send ","
|
||||
send $env("AUTH")
|
||||
|
||||
@@ -10,6 +10,8 @@ opengt
|
||||
get 1 "" $s
|
||||
print $s
|
||||
|
||||
send "AT+XDNS?^m"
|
||||
send "AT+"
|
||||
send $env("DNSQUERY")
|
||||
send "^m"
|
||||
get 1 "" $s
|
||||
print $s
|
||||
@@ -7,9 +7,16 @@ opengt
|
||||
send "AT+CMEE=2^m"
|
||||
send "AT+COPS=0^m"
|
||||
send "AT+CGPIAF=1,0,0,0^m"
|
||||
|
||||
if $env("PREFIX") = "xmm" goto sendxdns
|
||||
goto skipxdns
|
||||
|
||||
:sendxdns
|
||||
send "AT+XDNS="
|
||||
send $env("CID")
|
||||
send ",1^m"
|
||||
|
||||
:skipxdns
|
||||
send "AT+CGDCONT="
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
@@ -27,11 +34,17 @@ opengt
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
|
||||
if $env("PREFIX") = "xmm" goto senddatachannel
|
||||
goto skipdatachannel
|
||||
|
||||
:senddatachannel
|
||||
send "AT+XDATACHANNEL=1,1,\"/USBCDC/0\",\"/USBHS/NCM/0\",2,"
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
send "AT+CGDATA=\"M-RAW_IP\","
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
|
||||
:skipdatachannel
|
||||
get 1 "" $s
|
||||
print $s
|
||||
@@ -7,4 +7,5 @@ opengt
|
||||
send "AT+CGACT=0,"
|
||||
send $env("CID")
|
||||
send "^m"
|
||||
|
||||
send "AT+XDATACHANNEL=0^m"
|
||||
@@ -61,11 +61,15 @@ proto_xmm_setup() {
|
||||
PREFIX="xmm"
|
||||
hwaddr="$(ls -1 $devpath/../*/net/*/*address*)"
|
||||
XMMDNS="XDNS"
|
||||
AUTHCMD="XGAUTH"
|
||||
DNSQUERY="XDNS?"
|
||||
;;
|
||||
0e8d7126|0e8d7127)
|
||||
PREFIX="fm350"
|
||||
hwaddr="$(ls -1 $devpath/../../*/net/*/*address*)"
|
||||
XMMDNS="GTDNS"
|
||||
AUTHCMD="CGAUTH"
|
||||
DNSQUERY="GTDNS=$profile"
|
||||
;;
|
||||
*)
|
||||
echo "Modem not supported!"
|
||||
@@ -148,14 +152,14 @@ proto_xmm_setup() {
|
||||
chap) AUTH=2 ;;
|
||||
*) AUTH=0 ;;
|
||||
esac
|
||||
CID=$profile AUTH=$AUTH USER="$username" PASS="$password" gcom -d "$device" -s /etc/gcom/${PREFIX}-auth.gcom >/dev/null 2>&1
|
||||
CID=$profile AUTH=$AUTH AUTHCMD=$AUTHCMD USER="$username" PASS="$password" gcom -d "$device" -s /etc/gcom/xmm-auth.gcom >/dev/null 2>&1
|
||||
}
|
||||
|
||||
CID=$profile APN=$apn PDP=$pdp gcom -d $device -s /etc/gcom/${PREFIX}-connect.gcom >/dev/null 2>&1
|
||||
CID=$profile APN=$apn PDP=$pdp PREFIX=$PREFIX gcom -d $device -s /etc/gcom/xmm-connect.gcom >/dev/null 2>&1
|
||||
proto_init_update "$ifname" 1
|
||||
proto_add_data
|
||||
proto_close_data
|
||||
DATA=$(CID=$profile gcom -d $device -s /etc/gcom/${PREFIX}-config.gcom)
|
||||
DATA=$(CID=$profile DNSQUERY=$DNSQUERY gcom -d $device -s /etc/gcom/xmm-config.gcom)
|
||||
ip4addr=$(echo "$DATA" | awk -F [,] '/^\+CGPADDR/{gsub("\r|\"", ""); print $2}') >/dev/null 2>&1
|
||||
lladdr=$(echo "$DATA" | awk -F [,] '/^\+CGPADDR/{gsub("\r|\"", ""); print $3}') >/dev/null 2>&1
|
||||
ns=$(echo "$DATA" | awk -F [,] '/^\+'$XMMDNS': /{gsub("\r|\"",""); gsub("0.0.0.0",""); print $2" "$3}' | sed 's/^[[:space:]]//g' | uniq)
|
||||
|
||||
Reference in new issue
Block a user