mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 12:38:03 +08:00
🎉 Sync 2026-11-13 23:37:37
This commit is contained in:
1 parent
3d6b9219c2
commit
f42f484f1b
71 files changed
+3836
-467
No files matched your search
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=filebrowser-q
|
||||
PKG_VERSION:=2.0.2-stable
|
||||
PKG_RELEASE:=13
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/gtsteffaniak/filebrowser/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=libopenssl-gost
|
||||
PKG_VERSION:=3.0.5
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/gost-engine/engine.git
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,86 @@
|
||||
From d7932b33a779c08551d6cd44bf0eae3e250db91c Mon Sep 17 00:00:00 2001
|
||||
From: Kirill Ermoshin <ermoshin@rutoken.ru>
|
||||
Date: Tue, 10 Nov 2026 16:27:14 +0300
|
||||
Subject: [PATCH 1/4] Add digest stub with MAC for TLS 1.2
|
||||
|
||||
---
|
||||
gost_prov_digest.c | 20 +++++++++++++++++++-
|
||||
test_digest.c | 18 +++++++++++++++---
|
||||
2 files changed, 34 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/gost_prov_digest.c b/gost_prov_digest.c
|
||||
index 7a237e5..4ce22fb 100644
|
||||
--- a/gost_prov_digest.c
|
||||
+++ b/gost_prov_digest.c
|
||||
@@ -158,6 +158,11 @@ MAKE_FUNCTIONS(GostR3411_94_digest);
|
||||
MAKE_FUNCTIONS(GostR3411_2012_256_digest);
|
||||
MAKE_FUNCTIONS(GostR3411_2012_512_digest);
|
||||
|
||||
+MAKE_FUNCTIONS(Gost28147_89_mac);
|
||||
+MAKE_FUNCTIONS(Gost28147_89_mac_12);
|
||||
+MAKE_FUNCTIONS(magma_omac_mac);
|
||||
+MAKE_FUNCTIONS(grasshopper_omac_mac);
|
||||
+
|
||||
/* The OSSL_ALGORITHM for the provider's operation query function */
|
||||
const OSSL_ALGORITHM GOST_prov_digests[] = {
|
||||
/*
|
||||
@@ -184,13 +189,26 @@ const OSSL_ALGORITHM GOST_prov_digests[] = {
|
||||
{ SN_id_GostR3411_94":id-GostR3411-94:1.2.643.2.2.9:"
|
||||
LN_id_GostR3411_94, NULL,
|
||||
GostR3411_94_digest_functions, "GOST R 34.11-94" },
|
||||
- { NULL , NULL, NULL }
|
||||
+
|
||||
+ { SN_id_Gost28147_89_MAC ":" LN_id_Gost28147_89_MAC ":1.2.643.2.2.22", NULL,
|
||||
+ Gost28147_89_mac_functions, "gost mac stub for TLS 1.2" },
|
||||
+ { SN_gost_mac_12, NULL,
|
||||
+ Gost28147_89_mac_12_functions, "gost12 mac stub for TLS 1.2" },
|
||||
+ { SN_magma_mac, NULL,
|
||||
+ magma_omac_mac_functions, "magma mac stub for TLS 1.2" },
|
||||
+ { SN_kuznyechik_mac, NULL,
|
||||
+ grasshopper_omac_mac_functions, "kuznyechik mac stub for TLS 1.2" },
|
||||
+ { NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
static GOST_digest *digests[] = {
|
||||
&GostR3411_94_digest,
|
||||
&GostR3411_2012_256_digest,
|
||||
&GostR3411_2012_512_digest,
|
||||
+ &Gost28147_89_mac,
|
||||
+ &Gost28147_89_mac_12,
|
||||
+ &magma_omac_mac,
|
||||
+ &grasshopper_omac_mac,
|
||||
};
|
||||
|
||||
#define arraysize(l) (sizeof(l) / sizeof(l[0]))
|
||||
diff --git a/test_digest.c b/test_digest.c
|
||||
index 89f73d9..46aaa94 100644
|
||||
--- a/test_digest.c
|
||||
+++ b/test_digest.c
|
||||
@@ -824,9 +824,21 @@ static int do_test(const struct hash_testvec *tv)
|
||||
EVP_MAC *mac = NULL;
|
||||
|
||||
ERR_set_mark();
|
||||
- T((md = EVP_MD_fetch(NULL, tv->algname, NULL))
|
||||
- || (mac = EVP_MAC_fetch(NULL, tv->algname, NULL))
|
||||
- || (md = (EVP_MD *)EVP_get_digestbyname(tv->algname)));
|
||||
+ if (tv->key && !tv->hmac) {
|
||||
+ /*
|
||||
+ * The provider may expose a digest stub under the same name
|
||||
+ * (needed for TLS 1.2) which cannot accept a key through
|
||||
+ * EVP_MD_CTX_ctrl. Prefer the MAC implementation for keyed
|
||||
+ * non-HMAC vectors.
|
||||
+ */
|
||||
+ T((mac = EVP_MAC_fetch(NULL, tv->algname, NULL))
|
||||
+ || (md = EVP_MD_fetch(NULL, tv->algname, NULL))
|
||||
+ || (md = (EVP_MD *)EVP_get_digestbyname(tv->algname)));
|
||||
+ } else {
|
||||
+ T((md = EVP_MD_fetch(NULL, tv->algname, NULL))
|
||||
+ || (mac = EVP_MAC_fetch(NULL, tv->algname, NULL))
|
||||
+ || (md = (EVP_MD *)EVP_get_digestbyname(tv->algname)));
|
||||
+ }
|
||||
ERR_pop_to_mark();
|
||||
|
||||
printf(cBLUE "Test %s: %s: " cNORM, tv->algname, tv->name);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+617
@@ -0,0 +1,617 @@
|
||||
From aedda74bf7f09bcb520fe9f92b8f8b696b93c9f4 Mon Sep 17 00:00:00 2001
|
||||
From: Kirill Ermoshin <ermoshin@rutoken.ru>
|
||||
Date: Tue, 10 Nov 2026 14:02:42 +0300
|
||||
Subject: [PATCH 3/4] Add keymgmt for secret keys and MAC with sign provider
|
||||
interface
|
||||
|
||||
---
|
||||
cmake/provider.cmake | 2 +
|
||||
gost_prov.h | 31 +++++
|
||||
gost_prov_keymgmt.c | 8 ++
|
||||
gost_prov_keymgmt_mac.c | 186 ++++++++++++++++++++++++++
|
||||
gost_prov_signature.c | 24 ++++
|
||||
gost_prov_signature_mac.c | 273 ++++++++++++++++++++++++++++++++++++++
|
||||
6 files changed, 524 insertions(+)
|
||||
create mode 100644 gost_prov_keymgmt_mac.c
|
||||
create mode 100644 gost_prov_signature_mac.c
|
||||
|
||||
diff --git a/cmake/provider.cmake b/cmake/provider.cmake
|
||||
index 68c92d4..cac8a13 100644
|
||||
--- a/cmake/provider.cmake
|
||||
+++ b/cmake/provider.cmake
|
||||
@@ -5,6 +5,8 @@ set(GOST_PROV_SOURCE_FILES
|
||||
gost_prov_digest.c
|
||||
gost_prov_mac.c
|
||||
gost_prov_keymgmt.c
|
||||
+ gost_prov_keymgmt_mac.c
|
||||
+ gost_prov_signature_mac.c
|
||||
gost_prov_encoder.c
|
||||
gost_prov_signature.c
|
||||
gost_prov_decoder.c
|
||||
diff --git a/gost_prov.h b/gost_prov.h
|
||||
index 069fed7..f229dc2 100644
|
||||
--- a/gost_prov.h
|
||||
+++ b/gost_prov.h
|
||||
@@ -18,6 +18,7 @@
|
||||
#define OID_id_GostR3410_2012_256 "1.2.643.7.1.1.1.1"
|
||||
#define OID_id_GostR3410_2012_512 "1.2.643.7.1.1.1.2"
|
||||
#define OID_id_GostR3411_94_with_GostR3410_2001 "1.2.643.2.2.3"
|
||||
+#define OID_id_Gost28147_89_MAC "1.2.643.2.2.22"
|
||||
#define OID_id_tc26_signwithdigest_gost3410_2012_256 "1.2.643.7.1.1.3.2"
|
||||
#define OID_id_tc26_signwithdigest_gost3410_2012_512 "1.2.643.7.1.1.3.3"
|
||||
|
||||
@@ -42,6 +43,16 @@
|
||||
LN_id_GostR3410_2012_512 ":" \
|
||||
OID_id_GostR3410_2012_512
|
||||
|
||||
+/* Names for GOST MAC keymgmt and signature operations */
|
||||
+#define ALG_NAME_GOST_MAC \
|
||||
+ SN_id_Gost28147_89_MAC ":" \
|
||||
+ LN_id_Gost28147_89_MAC ":" \
|
||||
+ OID_id_Gost28147_89_MAC
|
||||
+
|
||||
+#define ALG_NAME_GOST12_MAC SN_gost_mac_12
|
||||
+#define ALG_NAME_MAGMA_MAC SN_magma_mac
|
||||
+#define ALG_NAME_KUZNYECHIK_MAC SN_kuznyechik_mac
|
||||
+
|
||||
/* Utilities for checking and working with bit flags */
|
||||
#define FLAGS_CONTAIN(flags, subset) (((flags)&(subset)) == (subset))
|
||||
#define FLAGS_INTERSECT(flags, subset) (((flags)&(subset)) != 0)
|
||||
@@ -64,6 +75,12 @@ typedef struct gost_key_data_st
|
||||
int param_nid;
|
||||
} GOST_KEY_DATA;
|
||||
|
||||
+/* Secret key data for GOST MAC operations (gost_prov_keymgmt_mac.c) */
|
||||
+struct gost_mac_key_data_st {
|
||||
+ unsigned char key[32];
|
||||
+};
|
||||
+typedef struct gost_mac_key_data_st GOST_MAC_KEY_DATA;
|
||||
+
|
||||
int gost_get_max_keyexch_size(const GOST_KEY_DATA *);
|
||||
int gost_get_max_signature_size(const GOST_KEY_DATA *);
|
||||
|
||||
@@ -74,3 +91,17 @@ extern const OSSL_ALGORITHM GOST_prov_signature[];
|
||||
extern const OSSL_ALGORITHM GOST_prov_decoder[];
|
||||
extern const OSSL_ALGORITHM GOST_prov_keyexch[];
|
||||
extern const OSSL_ALGORITHM GOST_prov_asym_cipher[];
|
||||
+
|
||||
+/* keymgmt for GOST MAC secret keys (gost_prov_keymgmt_mac.c) */
|
||||
+extern const OSSL_DISPATCH id_gost_mac_keymgmt_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_gost_mac_12_keymgmt_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_magma_mac_keymgmt_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_kuznyechik_mac_keymgmt_mac_functions[];
|
||||
+
|
||||
+/* MAC over signature API (gost_prov_signature_mac.c) */
|
||||
+extern const OSSL_DISPATCH id_Gost28147_89_mac_signature_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_Gost28147_89_mac_12_signature_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_magma_omac_mac_signature_mac_functions[];
|
||||
+extern const OSSL_DISPATCH id_grasshopper_omac_mac_signature_mac_functions[];
|
||||
+
|
||||
+int keymgmt_mac_gen_set_params(void *genctx, const OSSL_PARAM params[]);
|
||||
diff --git a/gost_prov_keymgmt.c b/gost_prov_keymgmt.c
|
||||
index f8ef3d0..b182db9 100644
|
||||
--- a/gost_prov_keymgmt.c
|
||||
+++ b/gost_prov_keymgmt.c
|
||||
@@ -801,5 +801,13 @@ const OSSL_ALGORITHM GOST_prov_keymgmt[] = {
|
||||
{ ALG_NAME_GOST2001DH, NULL, id_gost2001dh_keymgmt_functions },
|
||||
{ ALG_NAME_GOST2012_256, NULL, id_gost2012_256_keymgmt_functions },
|
||||
{ ALG_NAME_GOST2012_512, NULL, id_gost2012_512_keymgmt_functions },
|
||||
+ { ALG_NAME_GOST_MAC, NULL, id_gost_mac_keymgmt_mac_functions,
|
||||
+ "Keymgmt for gost-mac key" },
|
||||
+ { ALG_NAME_GOST12_MAC, NULL, id_gost_mac_12_keymgmt_mac_functions,
|
||||
+ "Keymgmt for gost-mac-12 key" },
|
||||
+ { ALG_NAME_MAGMA_MAC, NULL, id_magma_mac_keymgmt_mac_functions,
|
||||
+ "Keymgmt for magma-mac key" },
|
||||
+ { ALG_NAME_KUZNYECHIK_MAC, NULL, id_kuznyechik_mac_keymgmt_mac_functions,
|
||||
+ "Keymgmt for kuznyechik-mac key" },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
\ No newline at end of file
|
||||
diff --git a/gost_prov_keymgmt_mac.c b/gost_prov_keymgmt_mac.c
|
||||
new file mode 100644
|
||||
index 0000000..3d8f154
|
||||
--- /dev/null
|
||||
+++ b/gost_prov_keymgmt_mac.c
|
||||
@@ -0,0 +1,186 @@
|
||||
+/**********************************************************************
|
||||
+ * gost_prov_keymgmt_mac.c - keymgmt for MAC keys *
|
||||
+ * *
|
||||
+ * This file is distributed under the same license as OpenSSL *
|
||||
+ * *
|
||||
+ * Provider keymgmt for GOST MAC secret keys (gost-mac, *
|
||||
+ * gost-mac-12, magma-mac, kuznyechik-mac), requires OpenSSL 3.0 *
|
||||
+ **********************************************************************/
|
||||
+
|
||||
+#include <openssl/core_dispatch.h>
|
||||
+#include <openssl/core_names.h>
|
||||
+#include <openssl/params.h>
|
||||
+#include <openssl/rand.h>
|
||||
+
|
||||
+#include "gost_prov.h"
|
||||
+#include "gost_lcl.h"
|
||||
+
|
||||
+#define MAC_KEY_SIZE 32
|
||||
+
|
||||
+/*
|
||||
+ * Key data for GOST MAC operations is a 32-byte secret key (see
|
||||
+ * GOST_MAC_KEY_DATA in gost_prov.h).
|
||||
+ * There is no public part and no domain parameters, so most of the
|
||||
+ * generic keymgmt functions are stubs, like in the reference
|
||||
+ * rutoken-ossl-provider (src/secret/keymgmt_mac.cpp).
|
||||
+ */
|
||||
+
|
||||
+typedef struct gost_mac_gen_ctx_st {
|
||||
+ unsigned char key[MAC_KEY_SIZE];
|
||||
+ int key_set;
|
||||
+} GOST_MAC_GEN_CTX;
|
||||
+
|
||||
+static void *keymgmt_mac_new(void *provctx)
|
||||
+{
|
||||
+ (void)provctx;
|
||||
+ /* Key data can't be constructed without a key value */
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static void *keymgmt_mac_load(const void *reference, size_t reference_sz)
|
||||
+{
|
||||
+ (void)reference;
|
||||
+ (void)reference_sz;
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static int keymgmt_mac_has(const void *keydata, int selection)
|
||||
+{
|
||||
+ (void)keydata;
|
||||
+ (void)selection;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void keymgmt_mac_free(void *keydata)
|
||||
+{
|
||||
+ GOST_MAC_KEY_DATA *key = keydata;
|
||||
+
|
||||
+ if (!key)
|
||||
+ return;
|
||||
+
|
||||
+ OPENSSL_cleanse(key->key, sizeof(key->key));
|
||||
+ OPENSSL_free(key);
|
||||
+}
|
||||
+
|
||||
+static void keymgmt_mac_gen_cleanup(void *genctx)
|
||||
+{
|
||||
+ GOST_MAC_GEN_CTX *gctx = genctx;
|
||||
+
|
||||
+ if (!gctx)
|
||||
+ return;
|
||||
+
|
||||
+ OPENSSL_cleanse(gctx->key, sizeof(gctx->key));
|
||||
+ OPENSSL_free(gctx);
|
||||
+}
|
||||
+
|
||||
+static const OSSL_PARAM keymgmt_mac_gen_settable_params_list[] = {
|
||||
+ OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0),
|
||||
+ OSSL_PARAM_END
|
||||
+};
|
||||
+
|
||||
+static const OSSL_PARAM *keymgmt_mac_gen_settable_params(void *genctx,
|
||||
+ void *provctx)
|
||||
+{
|
||||
+ (void)genctx;
|
||||
+ (void)provctx;
|
||||
+ return keymgmt_mac_gen_settable_params_list;
|
||||
+}
|
||||
+
|
||||
+int keymgmt_mac_gen_set_params(void *genctx, const OSSL_PARAM params[])
|
||||
+{
|
||||
+ GOST_MAC_GEN_CTX *gctx = genctx;
|
||||
+ const OSSL_PARAM *p = NULL;
|
||||
+
|
||||
+ if (!gctx)
|
||||
+ return 0;
|
||||
+
|
||||
+ p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
|
||||
+ if (p == NULL)
|
||||
+ return 1;
|
||||
+
|
||||
+ if (p->data_type != OSSL_PARAM_OCTET_STRING || p->data_size != MAC_KEY_SIZE)
|
||||
+ return 0;
|
||||
+
|
||||
+ memcpy(gctx->key, p->data, MAC_KEY_SIZE);
|
||||
+ gctx->key_set = 1;
|
||||
+
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+static void *keymgmt_mac_gen_init(void *provctx, int selection,
|
||||
+ const OSSL_PARAM params[])
|
||||
+{
|
||||
+ (void)provctx;
|
||||
+
|
||||
+ if (selection != OSSL_KEYMGMT_SELECT_KEYPAIR)
|
||||
+ return NULL;
|
||||
+
|
||||
+ GOST_MAC_GEN_CTX *gctx = OPENSSL_zalloc(sizeof(GOST_MAC_GEN_CTX));
|
||||
+ if (!gctx)
|
||||
+ return NULL;
|
||||
+
|
||||
+ if (params && !keymgmt_mac_gen_set_params(gctx, params)) {
|
||||
+ keymgmt_mac_gen_cleanup(gctx);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ return gctx;
|
||||
+}
|
||||
+
|
||||
+static void *keymgmt_mac_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)
|
||||
+{
|
||||
+ GOST_MAC_GEN_CTX *gctx = genctx;
|
||||
+ GOST_MAC_KEY_DATA *key = NULL;
|
||||
+
|
||||
+ (void)osslcb;
|
||||
+ (void)cbarg;
|
||||
+
|
||||
+ if (!gctx)
|
||||
+ return NULL;
|
||||
+
|
||||
+ key = OPENSSL_zalloc(sizeof(GOST_MAC_KEY_DATA));
|
||||
+ if (!key)
|
||||
+ return NULL;
|
||||
+
|
||||
+ if (gctx->key_set) {
|
||||
+ memcpy(key->key, gctx->key, MAC_KEY_SIZE);
|
||||
+ } else if (RAND_bytes(key->key, MAC_KEY_SIZE) <= 0) {
|
||||
+ keymgmt_mac_free(key);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ return key;
|
||||
+}
|
||||
+
|
||||
+typedef void (*fptr_t)(void);
|
||||
+#define MAKE_KEYMGMT_MAC_FUNCTIONS(alg, alg_sn) \
|
||||
+ static OSSL_FUNC_keymgmt_query_operation_name_fn alg##_query_operation_name; \
|
||||
+ static const char *alg##_query_operation_name(int operation_id) \
|
||||
+ { \
|
||||
+ switch (operation_id) { \
|
||||
+ case OSSL_OP_SIGNATURE: \
|
||||
+ return alg_sn; \
|
||||
+ default: \
|
||||
+ return NULL; \
|
||||
+ } \
|
||||
+ } \
|
||||
+ const OSSL_DISPATCH id_##alg##_keymgmt_mac_functions[] = { \
|
||||
+ { OSSL_FUNC_KEYMGMT_NEW, (fptr_t)keymgmt_mac_new }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_FREE, (fptr_t)keymgmt_mac_free }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_HAS, (fptr_t)keymgmt_mac_has }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_LOAD, (fptr_t)keymgmt_mac_load }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_GEN_INIT, (fptr_t)keymgmt_mac_gen_init }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_GEN, (fptr_t)keymgmt_mac_gen }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (fptr_t)keymgmt_mac_gen_cleanup }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (fptr_t)keymgmt_mac_gen_set_params }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, \
|
||||
+ (fptr_t)keymgmt_mac_gen_settable_params }, \
|
||||
+ { OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, \
|
||||
+ (fptr_t)alg##_query_operation_name }, \
|
||||
+ OSSL_DISPATCH_END \
|
||||
+ };
|
||||
+
|
||||
+MAKE_KEYMGMT_MAC_FUNCTIONS(gost_mac, SN_id_Gost28147_89_MAC);
|
||||
+MAKE_KEYMGMT_MAC_FUNCTIONS(gost_mac_12, SN_gost_mac_12);
|
||||
+MAKE_KEYMGMT_MAC_FUNCTIONS(magma_mac, SN_magma_mac);
|
||||
+MAKE_KEYMGMT_MAC_FUNCTIONS(kuznyechik_mac, SN_kuznyechik_mac);
|
||||
diff --git a/gost_prov_signature.c b/gost_prov_signature.c
|
||||
index 1546cbb..69b0f70 100644
|
||||
--- a/gost_prov_signature.c
|
||||
+++ b/gost_prov_signature.c
|
||||
@@ -546,5 +546,29 @@ const OSSL_ALGORITHM GOST_prov_signature[] = {
|
||||
NULL,
|
||||
id_signature_functions
|
||||
},
|
||||
+ {
|
||||
+ ALG_NAME_GOST_MAC,
|
||||
+ NULL,
|
||||
+ id_Gost28147_89_mac_signature_mac_functions,
|
||||
+ "gost mac with sign interface"
|
||||
+ },
|
||||
+ {
|
||||
+ ALG_NAME_GOST12_MAC,
|
||||
+ NULL,
|
||||
+ id_Gost28147_89_mac_12_signature_mac_functions,
|
||||
+ "gost 12 mac with sign interface"
|
||||
+ },
|
||||
+ {
|
||||
+ ALG_NAME_MAGMA_MAC,
|
||||
+ NULL,
|
||||
+ id_magma_omac_mac_signature_mac_functions,
|
||||
+ "magma mac with sign interface"
|
||||
+ },
|
||||
+ {
|
||||
+ ALG_NAME_KUZNYECHIK_MAC,
|
||||
+ NULL,
|
||||
+ id_grasshopper_omac_mac_signature_mac_functions,
|
||||
+ "kuznyechik mac with sign interface"
|
||||
+ },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
diff --git a/gost_prov_signature_mac.c b/gost_prov_signature_mac.c
|
||||
new file mode 100644
|
||||
index 0000000..719192f
|
||||
--- /dev/null
|
||||
+++ b/gost_prov_signature_mac.c
|
||||
@@ -0,0 +1,273 @@
|
||||
+/**********************************************************************
|
||||
+ * gost_prov_signature_mac.c - MAC over signature API *
|
||||
+ * *
|
||||
+ * This file is distributed under the same license as OpenSSL *
|
||||
+ * *
|
||||
+ * Provider signature interface for GOST MAC secret keys *
|
||||
+ * (gost-mac, gost-mac-12, magma-mac, kuznyechik-mac), *
|
||||
+ * requires OpenSSL 3.0 *
|
||||
+ **********************************************************************/
|
||||
+
|
||||
+#include <openssl/core_dispatch.h>
|
||||
+#include <openssl/core_names.h>
|
||||
+#include <openssl/params.h>
|
||||
+
|
||||
+#include "gost_prov.h"
|
||||
+#include "gost_lcl.h"
|
||||
+
|
||||
+#define MAC_KEY_SIZE 32
|
||||
+
|
||||
+/*
|
||||
+ * MAC computation over the OSSL_OP_SIGNATURE interface.
|
||||
+ * The underlying MAC implementations are the same as
|
||||
+ * used in gost_prov_mac.c, so we reuse the
|
||||
+ * GOST_digest / GOST_digest_ctx wrappers.
|
||||
+ */
|
||||
+struct gost_sig_mac_desc_st {
|
||||
+ GOST_digest *digest;
|
||||
+ size_t initial_mac_size;
|
||||
+};
|
||||
+typedef struct gost_sig_mac_desc_st GOST_SIG_MAC_DESC;
|
||||
+
|
||||
+struct gost_sig_mac_ctx_st {
|
||||
+ PROV_CTX *provctx;
|
||||
+ const GOST_SIG_MAC_DESC *descriptor;
|
||||
+ /* Output MAC size */
|
||||
+ size_t mac_size;
|
||||
+ /* Saved key, needed to reset the MAC state for one-shot sign */
|
||||
+ unsigned char key[MAC_KEY_SIZE];
|
||||
+ GOST_digest_ctx *dctx;
|
||||
+};
|
||||
+typedef struct gost_sig_mac_ctx_st GOST_SIG_MAC_CTX;
|
||||
+
|
||||
+static void sig_mac_freectx(void *vsigctx)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+
|
||||
+ if (!sigctx)
|
||||
+ return;
|
||||
+
|
||||
+ /*
|
||||
+ * We don't free sigctx->descriptor->digest here.
|
||||
+ * That will be done by the provider teardown, via
|
||||
+ * GOST_prov_deinit_macs() (defined in gost_prov_mac.c).
|
||||
+ */
|
||||
+ OPENSSL_cleanse(sigctx->key, sizeof(sigctx->key));
|
||||
+ GOST_digest_ctx_free(sigctx->dctx);
|
||||
+ OPENSSL_free(sigctx);
|
||||
+}
|
||||
+
|
||||
+static GOST_SIG_MAC_CTX *sig_mac_newctx(void *provctx,
|
||||
+ const GOST_SIG_MAC_DESC *descriptor)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = NULL;
|
||||
+ const GOST_digest *digest = NULL;
|
||||
+
|
||||
+ if ((sigctx = OPENSSL_zalloc(sizeof(*sigctx))) != NULL) {
|
||||
+ sigctx->provctx = provctx;
|
||||
+ sigctx->descriptor = descriptor;
|
||||
+ sigctx->mac_size = descriptor->initial_mac_size;
|
||||
+ digest = GOST_digest_init(descriptor->digest);
|
||||
+ sigctx->dctx = GOST_digest_ctx_new();
|
||||
+
|
||||
+ if (digest == NULL
|
||||
+ || sigctx->dctx == NULL
|
||||
+ || GOST_digest_ctx_init(sigctx->dctx, digest) <= 0) {
|
||||
+ sig_mac_freectx(sigctx);
|
||||
+ sigctx = NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ return sigctx;
|
||||
+}
|
||||
+
|
||||
+static void *sig_mac_dupctx(void *vsigctx)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *src = vsigctx;
|
||||
+ GOST_SIG_MAC_CTX *dst = NULL;
|
||||
+
|
||||
+ if (!src)
|
||||
+ return NULL;
|
||||
+
|
||||
+ dst = sig_mac_newctx(src->provctx, src->descriptor);
|
||||
+ if (!dst)
|
||||
+ return NULL;
|
||||
+
|
||||
+ if (GOST_digest_ctx_copy(dst->dctx, src->dctx) <= 0) {
|
||||
+ sig_mac_freectx(dst);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ dst->mac_size = src->mac_size;
|
||||
+
|
||||
+ return dst;
|
||||
+}
|
||||
+
|
||||
+static int sig_mac_digest_sign_init(void *vsigctx, const char *mdname,
|
||||
+ void *provkey, const OSSL_PARAM params[])
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+ GOST_MAC_KEY_DATA *key = provkey;
|
||||
+
|
||||
+ (void)mdname;
|
||||
+ (void)params;
|
||||
+
|
||||
+ if (!sigctx || !key)
|
||||
+ return 0;
|
||||
+
|
||||
+ memcpy(sigctx->key, key->key, MAC_KEY_SIZE);
|
||||
+
|
||||
+ return GOST_digest_ctx_ctrl(sigctx->dctx, EVP_MD_CTRL_SET_KEY,
|
||||
+ MAC_KEY_SIZE, sigctx->key) > 0;
|
||||
+}
|
||||
+
|
||||
+static int sig_mac_digest_sign_update(void *vsigctx, const unsigned char *data,
|
||||
+ size_t datalen)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+
|
||||
+ if (!sigctx)
|
||||
+ return 0;
|
||||
+
|
||||
+ return GOST_digest_ctx_update(sigctx->dctx, data, datalen) > 0;
|
||||
+}
|
||||
+
|
||||
+static int sig_mac_final(GOST_SIG_MAC_CTX *sigctx, unsigned char *sig,
|
||||
+ size_t *siglen, size_t sigsize)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ if (siglen == NULL)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (sig == NULL) {
|
||||
+ *siglen = sigctx->mac_size;
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (sigsize < sigctx->mac_size)
|
||||
+ return 0;
|
||||
+
|
||||
+ /* We ignore the error for GOST MDs that don't support setting the size */
|
||||
+ GOST_digest_ctx_ctrl(sigctx->dctx, EVP_MD_CTRL_XOF_LEN,
|
||||
+ (int)sigctx->mac_size, NULL);
|
||||
+ ret = GOST_digest_ctx_final(sigctx->dctx, sig);
|
||||
+ if (ret > 0) {
|
||||
+ *siglen = sigctx->mac_size;
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int sig_mac_digest_sign_final(void *vsigctx, unsigned char *sig,
|
||||
+ size_t *siglen, size_t sigsize)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+
|
||||
+ if (!sigctx)
|
||||
+ return 0;
|
||||
+
|
||||
+ return sig_mac_final(sigctx, sig, siglen, sigsize);
|
||||
+}
|
||||
+
|
||||
+static int sig_mac_digest_sign(void *vsigctx, unsigned char *sig,
|
||||
+ size_t *siglen, size_t sigsize,
|
||||
+ const unsigned char *tbs, size_t tbslen)
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+
|
||||
+ if (!sigctx)
|
||||
+ return 0;
|
||||
+
|
||||
+ /*
|
||||
+ * One-shot sign is independent of any previously processed data.
|
||||
+ * EVP_DigestSign() may be called twice (to query the size and to
|
||||
+ * compute the signature), so reset the MAC state by setting the
|
||||
+ * key again: the SET_KEY ctrl re-initializes the underlying
|
||||
+ * digest context.
|
||||
+ */
|
||||
+ if (GOST_digest_ctx_ctrl(sigctx->dctx, EVP_MD_CTRL_SET_KEY,
|
||||
+ MAC_KEY_SIZE, sigctx->key) <= 0)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (!sig_mac_digest_sign_update(sigctx, tbs, tbslen))
|
||||
+ return 0;
|
||||
+
|
||||
+ return sig_mac_final(sigctx, sig, siglen, sigsize);
|
||||
+}
|
||||
+
|
||||
+/* https://github.com/openssl/openssl/pull/29921 */
|
||||
+#ifndef OSSL_DIGEST_PARAM_TLSTREE
|
||||
+#define OSSL_DIGEST_PARAM_TLSTREE "tlstree"
|
||||
+#endif
|
||||
+
|
||||
+static int sig_mac_set_ctx_md_params(void *vsigctx, const OSSL_PARAM params[])
|
||||
+{
|
||||
+ GOST_SIG_MAC_CTX *sigctx = vsigctx;
|
||||
+ const OSSL_PARAM *p = NULL;
|
||||
+
|
||||
+ if (params == NULL)
|
||||
+ return 1;
|
||||
+
|
||||
+ p = OSSL_PARAM_locate_const(params, OSSL_DIGEST_PARAM_TLSTREE);
|
||||
+ if (p != NULL) {
|
||||
+ const void *tlstree = NULL;
|
||||
+ size_t tlstree_len = 0;
|
||||
+
|
||||
+ if (!OSSL_PARAM_get_octet_string_ptr(p, &tlstree, &tlstree_len))
|
||||
+ return 0;
|
||||
+
|
||||
+ if (GOST_digest_ctx_ctrl(sigctx->dctx, EVP_MD_CTRL_TLSTREE,
|
||||
+ 0, (void *)tlstree) <= 0)
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+static const OSSL_PARAM sig_mac_settable_ctx_md_params_list[] = {
|
||||
+ OSSL_PARAM_octet_string(OSSL_DIGEST_PARAM_TLSTREE, NULL, 0),
|
||||
+ OSSL_PARAM_END
|
||||
+};
|
||||
+
|
||||
+static const OSSL_PARAM *sig_mac_settable_ctx_md_params(void *vsigctx,
|
||||
+ void *provctx)
|
||||
+{
|
||||
+ (void)vsigctx;
|
||||
+ (void)provctx;
|
||||
+ return sig_mac_settable_ctx_md_params_list;
|
||||
+}
|
||||
+
|
||||
+typedef void (*fptr_t)(void);
|
||||
+#define MAKE_SIG_MAC_FUNCTIONS(name, macsize) \
|
||||
+ const GOST_SIG_MAC_DESC name##_sig_mac_desc = { \
|
||||
+ &name, \
|
||||
+ macsize, \
|
||||
+ }; \
|
||||
+ static OSSL_FUNC_signature_newctx_fn name##_newctx; \
|
||||
+ static void *name##_newctx(void *provctx, const char *propq) \
|
||||
+ { \
|
||||
+ (void)propq; \
|
||||
+ return sig_mac_newctx(provctx, &name##_sig_mac_desc); \
|
||||
+ } \
|
||||
+ const OSSL_DISPATCH id_##name##_signature_mac_functions[] = { \
|
||||
+ { OSSL_FUNC_SIGNATURE_NEWCTX, (fptr_t)name##_newctx }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_FREECTX, (fptr_t)sig_mac_freectx }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_DUPCTX, (fptr_t)sig_mac_dupctx }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_INIT, \
|
||||
+ (fptr_t)sig_mac_digest_sign_init }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_UPDATE, \
|
||||
+ (fptr_t)sig_mac_digest_sign_update }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_DIGEST_SIGN_FINAL, \
|
||||
+ (fptr_t)sig_mac_digest_sign_final }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_DIGEST_SIGN, (fptr_t)sig_mac_digest_sign }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_SET_CTX_MD_PARAMS, \
|
||||
+ (fptr_t)sig_mac_set_ctx_md_params }, \
|
||||
+ { OSSL_FUNC_SIGNATURE_SETTABLE_CTX_MD_PARAMS, \
|
||||
+ (fptr_t)sig_mac_settable_ctx_md_params }, \
|
||||
+ OSSL_DISPATCH_END \
|
||||
+ }
|
||||
+
|
||||
+MAKE_SIG_MAC_FUNCTIONS(Gost28147_89_mac, 4);
|
||||
+MAKE_SIG_MAC_FUNCTIONS(Gost28147_89_mac_12, 4);
|
||||
+MAKE_SIG_MAC_FUNCTIONS(magma_omac_mac, 8);
|
||||
+MAKE_SIG_MAC_FUNCTIONS(grasshopper_omac_mac, 16);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
From c2b4ae50479d12a2f72914595a1cb3d24bd6e831 Mon Sep 17 00:00:00 2001
|
||||
From: Kirill Ermoshin <ermoshin@rutoken.ru>
|
||||
Date: Tue, 10 Nov 2026 16:09:05 +0300
|
||||
Subject: [PATCH 4/4] Add OSSL_CIPHER_PARAM_TLS_MAC handler for ciphers
|
||||
|
||||
We need cut MAC during decryption for TLS 1.2
|
||||
---
|
||||
gost_prov_cipher.c | 54 +++++++++++++++++++++++++++++--
|
||||
test_ciphers.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 132 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/gost_prov_cipher.c b/gost_prov_cipher.c
|
||||
index 7a0a02d..0a7472c 100644
|
||||
--- a/gost_prov_cipher.c
|
||||
+++ b/gost_prov_cipher.c
|
||||
@@ -118,6 +118,13 @@ struct gost_prov_crypt_ctx_st {
|
||||
GOST_cipher *cipher;
|
||||
/* The context for the GOST_cipher functions */
|
||||
GOST_cipher_ctx *cctx;
|
||||
+ /*
|
||||
+ * TLS MAC extracted from the tail of decrypted records, it is
|
||||
+ * requested by libssl through OSSL_CIPHER_PARAM_TLS_MAC (octet_ptr).
|
||||
+ * The size is set by libssl via OSSL_CIPHER_PARAM_TLS_MAC_SIZE.
|
||||
+ */
|
||||
+ unsigned char *tls_mac;
|
||||
+ size_t tls_mac_size;
|
||||
};
|
||||
typedef struct gost_prov_crypt_ctx_st GOST_CTX;
|
||||
|
||||
@@ -147,6 +154,7 @@ static void cipher_freectx(void *vgctx)
|
||||
if (gctx == NULL)
|
||||
return;
|
||||
GOST_cipher_ctx_free(gctx->cctx);
|
||||
+ OPENSSL_clear_free(gctx->tls_mac, gctx->tls_mac_size);
|
||||
OPENSSL_free(gctx);
|
||||
}
|
||||
|
||||
@@ -314,6 +322,14 @@ static int cipher_get_ctx_params(void *vgctx, OSSL_PARAM params[])
|
||||
if (!OSSL_PARAM_set_int(p, nid))
|
||||
return 0;
|
||||
}
|
||||
+ if ((p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_TLS_MAC)) != NULL) {
|
||||
+ /*
|
||||
+ * libssl reads the MAC extracted from the decrypted record tail
|
||||
+ * (see cipher_update) through the octet_ptr parameter.
|
||||
+ */
|
||||
+ if (!OSSL_PARAM_set_octet_ptr(p, gctx->tls_mac, gctx->tls_mac_size))
|
||||
+ return 0;
|
||||
+ }
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -383,6 +399,22 @@ static int cipher_set_ctx_params(void *vgctx, const OSSL_PARAM params[])
|
||||
(int)arg, (void *)val) <= 0)
|
||||
return 0;
|
||||
}
|
||||
+ if ((p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_TLS_MAC_SIZE)) != NULL) {
|
||||
+ size_t mac_size = 0;
|
||||
+ if (!OSSL_PARAM_get_size_t(p, &mac_size))
|
||||
+ return 0;
|
||||
+ if (mac_size > EVP_MAX_MD_SIZE)
|
||||
+ return 0;
|
||||
+ OPENSSL_clear_free(gctx->tls_mac, gctx->tls_mac_size);
|
||||
+ gctx->tls_mac = NULL;
|
||||
+ gctx->tls_mac_size = 0;
|
||||
+ if (mac_size != 0) {
|
||||
+ gctx->tls_mac = OPENSSL_malloc(mac_size);
|
||||
+ if (gctx->tls_mac == NULL)
|
||||
+ return 0;
|
||||
+ gctx->tls_mac_size = mac_size;
|
||||
+ }
|
||||
+ }
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -422,8 +454,26 @@ static int cipher_update(void *vgctx,
|
||||
int int_outl = outl != NULL ? *outl : 0;
|
||||
int res = GOST_CipherUpdate(gctx->cctx, out, &int_outl, in, (int)inl);
|
||||
|
||||
- if (res > 0 && outl != NULL)
|
||||
- *outl = (size_t)int_outl;
|
||||
+ if (res > 0) {
|
||||
+ /*
|
||||
+ * In TLS the MAC is encrypted together with the data, libssl
|
||||
+ * expects the provider to cut the MAC off the tail of the
|
||||
+ * decrypted record and to hand it over later through the
|
||||
+ * OSSL_CIPHER_PARAM_TLS_MAC (octet_ptr) parameter. This works
|
||||
+ * only for stream ciphers which don't buffer data internally,
|
||||
+ * i.e. all GOST TLS 1.2 suites.
|
||||
+ */
|
||||
+ if (!GOST_cipher_ctx_encrypting(gctx->cctx)
|
||||
+ && gctx->tls_mac_size > 0) {
|
||||
+ if (out == NULL || int_outl < 0
|
||||
+ || (size_t)int_outl < gctx->tls_mac_size)
|
||||
+ return 0;
|
||||
+ int_outl -= (int)gctx->tls_mac_size;
|
||||
+ memcpy(gctx->tls_mac, out + int_outl, gctx->tls_mac_size);
|
||||
+ }
|
||||
+ if (outl != NULL)
|
||||
+ *outl = (size_t)int_outl;
|
||||
+ }
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
diff --git a/test_ciphers.c b/test_ciphers.c
|
||||
index 7508a82..ed8e382 100644
|
||||
--- a/test_ciphers.c
|
||||
+++ b/test_ciphers.c
|
||||
@@ -858,6 +858,82 @@ static int test_provider_padded_decrypt(const EVP_CIPHER *type, const char *name
|
||||
return ret;
|
||||
}
|
||||
|
||||
+/* Size of the MAC (imit) for the GOST TLS 1.2 stream cipher suites */
|
||||
+static size_t tls_mac_size_for_cipher(const char *name)
|
||||
+{
|
||||
+ if (strcmp(name, SN_grasshopper_ctr) == 0
|
||||
+ || strcmp(name, SN_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm) == 0)
|
||||
+ return 16;
|
||||
+ return 8;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Emulates the libssl record protection for Mac-then-encrypt stream
|
||||
+ * ciphers: the MAC is encrypted as a tail of the record, on decryption
|
||||
+ * the provider must cut it off the output and hand it over through the
|
||||
+ * OSSL_CIPHER_PARAM_TLS_MAC (octet_ptr) parameter.
|
||||
+ */
|
||||
+static int test_provider_tls_mac(const EVP_CIPHER *type, const char *name,
|
||||
+ const unsigned char *key,
|
||||
+ const unsigned char *iv, size_t iv_size,
|
||||
+ size_t mac_size)
|
||||
+{
|
||||
+ EVP_CIPHER_CTX *enc = NULL, *dec = NULL;
|
||||
+ OSSL_PARAM params[2];
|
||||
+ unsigned char rec[128];
|
||||
+ unsigned char ct[128];
|
||||
+ unsigned char *tls_mac = NULL;
|
||||
+ int outl = 0, part = 0;
|
||||
+ int ret = 0, test;
|
||||
+ size_t i;
|
||||
+
|
||||
+ if (EVP_CIPHER_get0_provider(type) == NULL)
|
||||
+ return 0;
|
||||
+
|
||||
+ T(mac_size > 0 && mac_size <= EVP_MAX_MD_SIZE);
|
||||
+ T(sizeof(rec) >= sizeof(P) + mac_size);
|
||||
+ memcpy(rec, P, sizeof(P));
|
||||
+ for (i = 0; i < mac_size; i++)
|
||||
+ rec[sizeof(P) + i] = (unsigned char)(0xa0 + i);
|
||||
+
|
||||
+ printf("Provider tls-mac test [%s]: ", name);
|
||||
+
|
||||
+ enc = EVP_CIPHER_CTX_new();
|
||||
+ dec = EVP_CIPHER_CTX_new();
|
||||
+ T(enc != NULL && dec != NULL);
|
||||
+
|
||||
+ /* libssl sets tls-mac-size on both directions */
|
||||
+ params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_TLS_MAC_SIZE, &mac_size);
|
||||
+ params[1] = OSSL_PARAM_construct_end();
|
||||
+
|
||||
+ T(EVP_CipherInit_ex(enc, type, NULL, key, iv, 1));
|
||||
+ T(EVP_CIPHER_CTX_set_params(enc, params));
|
||||
+ /* Nothing must be cut off the output while encrypting */
|
||||
+ T(EVP_EncryptUpdate(enc, ct, &outl, rec, (int)(sizeof(P) + mac_size)));
|
||||
+ TEST_ASSERT((size_t)outl != sizeof(P) + mac_size);
|
||||
+ ret |= test;
|
||||
+
|
||||
+ T(EVP_CipherInit_ex(dec, type, NULL, key, iv, 0));
|
||||
+ T(EVP_CIPHER_CTX_set_params(dec, params));
|
||||
+ T(EVP_DecryptUpdate(dec, ct, &part, ct, outl));
|
||||
+ /* The plaintext must come out without the MAC tail */
|
||||
+ TEST_ASSERT((size_t)part != sizeof(P) || memcmp(ct, P, sizeof(P)) != 0);
|
||||
+ ret |= test;
|
||||
+
|
||||
+ params[0] = OSSL_PARAM_construct_octet_ptr(OSSL_CIPHER_PARAM_TLS_MAC,
|
||||
+ (void **)&tls_mac, mac_size);
|
||||
+ params[1] = OSSL_PARAM_construct_end();
|
||||
+ T(EVP_CIPHER_CTX_get_params(dec, params));
|
||||
+ T(params[0].return_size == mac_size);
|
||||
+ TEST_ASSERT(tls_mac == NULL
|
||||
+ || memcmp(tls_mac, rec + sizeof(P), mac_size) != 0);
|
||||
+ ret |= test;
|
||||
+
|
||||
+ EVP_CIPHER_CTX_free(enc);
|
||||
+ EVP_CIPHER_CTX_free(dec);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
int engine_is_available(const char *name)
|
||||
{
|
||||
#if HAVE_ENGINE
|
||||
@@ -963,6 +1039,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
ret |= test_provider_padded_decrypt(ciph, t->algname, t->block,
|
||||
t->key, t->iv);
|
||||
+ if (t->stream || strcmp(t->algname, SN_magma_ctr) == 0)
|
||||
+ ret |= test_provider_tls_mac(ciph, t->algname, t->key,
|
||||
+ t->iv, t->iv_size,
|
||||
+ tls_mac_size_for_cipher(t->algname));
|
||||
}
|
||||
ret |= test_engine_asn1_acpkm_reinit(ciph, t->algname, t->key, t->iv);
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -12,7 +12,7 @@ MAINTAINER:=Rafał Wabik <4Rafal@gmail.com>
|
||||
LUCI_DESCRIPTION:=Super simple graphical configurator for crontab.
|
||||
LUCI_DEPENDS:=
|
||||
PKG_VERSION:=1.0.4
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
@@ -7,196 +7,280 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: luci-app-crontab-wizard/root/usr/share/luci/menu.d/luci-app-crontab-wizard.json
|
||||
#: luci-app-crontab-wizard/root/usr/share/luci/menu.d/luci-app-crontab-wizard.json:3
|
||||
#: luci-app-crontab-wizard/root/usr/share/luci/menu.d/luci-app-crontab-wizard.json:15
|
||||
msgid "Scheduled Tasks"
|
||||
msgstr "计划任务"
|
||||
|
||||
#: luci-app-crontab-wizard/root/usr/share/luci/menu.d/luci-app-crontab-wizard.json:27
|
||||
msgid "Crontab Configurator"
|
||||
msgstr "Crontab 配置向导"
|
||||
msgstr "任务配置向导"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:559
|
||||
msgid "Graphical Crontab Configurator"
|
||||
msgstr "图形化 Crontab 配置向导"
|
||||
msgstr "可视化计划任务配置器"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:561
|
||||
msgid "A super simple graphical configurator for crontab."
|
||||
msgstr "一个超级简单的图形化 Crontab 定时任务配置工具。"
|
||||
msgstr "简单直观的 Crontab 计划任务可视化配置与生成工具。"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:567
|
||||
msgid "Minute"
|
||||
msgstr "分钟"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:585
|
||||
msgid "every specified minute period"
|
||||
msgstr "每隔指定分钟周期"
|
||||
msgstr "按所选分钟间隔循环 (*/N)"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:591
|
||||
msgid "Hour"
|
||||
msgstr "小时"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:609
|
||||
msgid "every specified hour period"
|
||||
msgstr "每隔指定小时周期"
|
||||
msgstr "按所选小时间隔循环 (*/N)"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:615
|
||||
msgid "Day"
|
||||
msgstr "日期"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:633
|
||||
msgid "every specified day period"
|
||||
msgstr "每隔指定天数周期"
|
||||
msgstr "按所选天数间隔循环 (*/N)"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:639
|
||||
msgid "Month"
|
||||
msgstr "月份"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:662
|
||||
msgid "Weekday"
|
||||
msgstr "星期"
|
||||
|
||||
msgid "Command to execute:"
|
||||
msgstr "要执行的命令:"
|
||||
|
||||
msgid "Command preview:"
|
||||
msgstr "命令预览:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "规则说明:"
|
||||
|
||||
msgid "Generated cron command:"
|
||||
msgstr "生成的 Cron 命令:"
|
||||
|
||||
msgid "Cron command for copying"
|
||||
msgstr "用于复制的 Cron 命令"
|
||||
|
||||
msgid "Add to cron"
|
||||
msgstr "添加到 Crontab"
|
||||
|
||||
msgid "Reset changes"
|
||||
msgstr "重置更改"
|
||||
|
||||
msgid "Please enter the command"
|
||||
msgstr "请输入命令"
|
||||
|
||||
msgid "Check the cron entry, it must contain (time + command)"
|
||||
msgstr "请检查 Cron 条目,必须包含(时间 + 命令)"
|
||||
|
||||
msgid "This entry already exists in cron"
|
||||
msgstr "此条目已存在于 Crontab 中"
|
||||
|
||||
msgid "Added entry to cron"
|
||||
msgstr "已将条目添加到 Crontab"
|
||||
|
||||
msgid "Error: %s"
|
||||
msgstr "错误:%s"
|
||||
|
||||
msgid "Command \"%s\" "
|
||||
msgstr "命令 \"%s\" "
|
||||
|
||||
msgid "Command"
|
||||
msgstr "命令"
|
||||
|
||||
msgid "Command \"%s\" will run"
|
||||
msgstr "命令 \"%s\" 将运行"
|
||||
|
||||
msgid "Command will run"
|
||||
msgstr "命令将运行"
|
||||
|
||||
msgid "at time: %s"
|
||||
msgstr "时间:%s"
|
||||
|
||||
msgid "at times: %s"
|
||||
msgstr "时间:%s"
|
||||
|
||||
msgid "runs every %d %s"
|
||||
msgstr "每 %d %s运行一次"
|
||||
|
||||
msgid "in minutes: %s"
|
||||
msgstr "在指定分钟:%s"
|
||||
|
||||
msgid "in hours: %s"
|
||||
msgstr "在指定小时:%s"
|
||||
|
||||
msgid "runs every %d %s of the month"
|
||||
msgstr "每月每 %d %s运行一次"
|
||||
|
||||
msgid "on day(s): %s"
|
||||
msgstr "在日期:%s"
|
||||
|
||||
msgid "runs every %d month(s)"
|
||||
msgstr "每 %d 个月运行一次"
|
||||
|
||||
msgid "in month(s): %s"
|
||||
msgstr "在月份:%s"
|
||||
|
||||
msgid "runs every %d day of the week"
|
||||
msgstr "每周每 %d 天运行一次"
|
||||
|
||||
msgid "on weekday(s): %s"
|
||||
msgstr "在星期:%s"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "分钟"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "分钟"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "小时"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "小时"
|
||||
|
||||
msgid "day"
|
||||
msgstr "天"
|
||||
|
||||
msgid "days"
|
||||
msgstr "天"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:34
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:673
|
||||
msgid "Sunday"
|
||||
msgstr "星期日"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:35
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:674
|
||||
msgid "Monday"
|
||||
msgstr "星期一"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:36
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:675
|
||||
msgid "Tuesday"
|
||||
msgstr "星期二"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:37
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:676
|
||||
msgid "Wednesday"
|
||||
msgstr "星期三"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:38
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:677
|
||||
msgid "Thursday"
|
||||
msgstr "星期四"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:39
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:678
|
||||
msgid "Friday"
|
||||
msgstr "星期五"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:40
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:679
|
||||
msgid "Saturday"
|
||||
msgstr "星期六"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:45
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:647
|
||||
msgid "January"
|
||||
msgstr "一月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:46
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:648
|
||||
msgid "February"
|
||||
msgstr "二月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:47
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:649
|
||||
msgid "March"
|
||||
msgstr "三月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:48
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:650
|
||||
msgid "April"
|
||||
msgstr "四月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:49
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:651
|
||||
msgid "May"
|
||||
msgstr "五月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:50
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:652
|
||||
msgid "June"
|
||||
msgstr "六月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:51
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:653
|
||||
msgid "July"
|
||||
msgstr "七月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:52
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:654
|
||||
msgid "August"
|
||||
msgstr "八月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:53
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:655
|
||||
msgid "September"
|
||||
msgstr "九月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:54
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:656
|
||||
msgid "October"
|
||||
msgstr "十月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:55
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:657
|
||||
msgid "November"
|
||||
msgstr "十一月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:56
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:658
|
||||
msgid "December"
|
||||
msgstr "十二月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:97
|
||||
msgid "minute"
|
||||
msgstr "分钟"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:97
|
||||
msgid "minutes"
|
||||
msgstr "分钟"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:98
|
||||
msgid "hour"
|
||||
msgstr "小时"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:98
|
||||
msgid "hours"
|
||||
msgstr "小时"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:99
|
||||
msgid "day"
|
||||
msgstr "天"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:99
|
||||
msgid "days"
|
||||
msgstr "天"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:121
|
||||
msgid "at time: %s"
|
||||
msgstr "时间点为 %s"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:122
|
||||
msgid "at times: %s"
|
||||
msgstr "时间点为 %s"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:128
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:136
|
||||
msgid "runs every %d %s"
|
||||
msgstr "每 %d %s执行一次"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:131
|
||||
msgid "in minutes: %s"
|
||||
msgstr "在第 %s 分钟"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:139
|
||||
msgid "in hours: %s"
|
||||
msgstr "在 %s 时"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:148
|
||||
msgid "runs every %d %s of the month"
|
||||
msgstr "每月每隔 %d %s"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:152
|
||||
msgid "on day(s): %s"
|
||||
msgstr "日期为每月 %s 日"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:157
|
||||
msgid "runs every %d month(s)"
|
||||
msgstr "每 %d 个月"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:160
|
||||
msgid "in month(s): %s"
|
||||
msgstr "月份为 %s"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:165
|
||||
msgid "runs every %d day of the week"
|
||||
msgstr "每周每隔 %d 天"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:168
|
||||
msgid "on weekday(s): %s"
|
||||
msgstr "星期为 %s"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:181
|
||||
msgid "Command \"%s\" "
|
||||
msgstr "命令 \"%s\"(默认每分钟执行)"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:181
|
||||
msgid "Command"
|
||||
msgstr "尚未设置时间规则或命令"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:200
|
||||
msgid "Command \"%s\" will run"
|
||||
msgstr "命令 \"%s\" 将按以下规则执行:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:200
|
||||
msgid "Command will run"
|
||||
msgstr "命令将按以下规则执行:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:686
|
||||
msgid "Command to execute:"
|
||||
msgstr "要执行的命令:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:695
|
||||
msgid "Command preview:"
|
||||
msgstr "表达式高亮预览:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:704
|
||||
msgid "Description:"
|
||||
msgstr "执行规则说明:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:713
|
||||
msgid "Generated cron command:"
|
||||
msgstr "生成的 Cron 表达式:"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:720
|
||||
msgid "Cron command for copying"
|
||||
msgstr "可用于复制的完整 Cron 表达式"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:726
|
||||
msgid "Add to cron"
|
||||
msgstr "添加到计划任务"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:742
|
||||
msgid "Reset changes"
|
||||
msgstr "重置选项"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:500
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:512
|
||||
msgid "Please enter the command"
|
||||
msgstr "请输入要执行的命令"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:508
|
||||
msgid "Check the cron entry, it must contain (time + command)"
|
||||
msgstr "请检查计划任务条目,必须同时包含时间规则与执行命令"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:521
|
||||
msgid "This entry already exists in cron"
|
||||
msgstr "计划任务中已存在相同的条目"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:531
|
||||
msgid "Added entry to cron"
|
||||
msgstr "已成功添加至计划任务"
|
||||
|
||||
#: luci-app-crontab-wizard/htdocs/luci-static/resources/view/system/crontab_wizard.js:534
|
||||
msgid "Error: %s"
|
||||
msgstr "错误:%s"
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-trafficctl
|
||||
PKG_VERSION:=1.30.5
|
||||
PKG_RELEASE:=32
|
||||
PKG_VERSION:=1.30.6
|
||||
PKG_RELEASE:=33
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
|
||||
|
||||
+251
-212
@@ -1,443 +1,482 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: luci-app-upnp\n"
|
||||
"POT-Creation-Date: 2026-11-13 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-11-13 23:30+0800\n"
|
||||
"Last-Translator: OpenWrt LuCI Localization\n"
|
||||
"Language-Team: Chinese (Simplified)\n"
|
||||
"Language: zh_Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "Unknown"
|
||||
msgstr "未知"
|
||||
#: applications/luci-app-upnp/root/usr/share/luci/menu.d/luci-app-upnp.json:3
|
||||
msgid "UPnP IGD & PCP"
|
||||
msgstr "UPnP IGD 与 PCP"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:24
|
||||
msgid "Active UPnP IGD & PCP/NAT-PMP Port Maps"
|
||||
msgstr "已生效的 UPnP IGD 与 PCP/NAT-PMP 端口映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "Not yet deletable; UPnP IGDv2 IPv6 port maps expire sooner, have no description"
|
||||
msgstr "暂不可删除;UPnP IGDv2 IPv6 端口映射过期更快且无描述信息"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "No active IPv4/IPv6 port maps"
|
||||
msgstr "没有活动的 IPv4/IPv6 端口映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgctxt "%s & %s/%s (%s = UPnP IGD, %s = PCP, %s = NAT-PMP)"
|
||||
msgid "%s & %s/%s"
|
||||
msgstr "%s & %s/%s"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "UPnP IGD & PCP/NAT-PMP Service"
|
||||
msgstr "UPnP IGD & PCP/NAT-PMP 服务"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgctxt "The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router."
|
||||
msgid "The %s protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router."
|
||||
msgstr "%s 协议/服务允许本地网络中受许可的设备在此路由器上自动建立 IPv4/IPv6 端口映射/转发。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "No suitable configuration was found!"
|
||||
msgstr "未找到合适的配置文件!"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing."
|
||||
msgstr "未找到适配的配置(LuCI 应用 %s,路径 %s)。可能缺少相关的软件包更新(守护进程或 LuCI 应用)。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update."
|
||||
msgstr "请使用软件包管理器更新列表并安装相关更新。配置文件将随守护进程软件包更新自动迁移。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgid "Go to package manager…"
|
||||
msgstr "前往软件包管理器…"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:35
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:76
|
||||
msgid "Hostname"
|
||||
msgstr "主机名"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:36
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:77
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:322
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:330
|
||||
msgid "IP address"
|
||||
msgstr "IP 地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:37
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:78
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:338
|
||||
msgid "Port"
|
||||
msgstr "端口"
|
||||
msgstr "内部端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:38
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:79
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:345
|
||||
msgid "External port"
|
||||
msgstr "外部端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:39
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:80
|
||||
msgid "Protocol"
|
||||
msgstr "协议"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:40
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:81
|
||||
msgid "Expires"
|
||||
msgstr "剩余时间"
|
||||
msgstr "剩余有效期"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:41
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:82
|
||||
msgid "Added via / description"
|
||||
msgstr "添加方式 / 描述"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:62
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:37
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:90
|
||||
msgid "Unknown"
|
||||
msgstr "未知"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:72
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:73
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:78
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:47
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:48
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:53
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:100
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:101
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:106
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:76
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:51
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:104
|
||||
msgid "Not yet deletable; UPnP IGDv2 IPv6 port maps expire sooner, have no description"
|
||||
msgstr "暂不支持手动删除;UPnP IGDv2 的 IPv6 端口映射无描述信息且会较快自动过期"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js:82
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:57
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:110
|
||||
msgid "No active IPv4/IPv6 port maps"
|
||||
msgstr "当前没有活动的 IPv4/IPv6 端口映射"
|
||||
|
||||
#. %s & %s/%s (%s = UPnP IGD, %s = PCP, %s = NAT-PMP)
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:62
|
||||
msgid "%s & %s/%s"
|
||||
msgstr "%s 与 %s/%s"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:63
|
||||
msgid "UPnP IGD & PCP/NAT-PMP Service"
|
||||
msgstr "UPnP IGD 与 PCP/NAT-PMP 服务"
|
||||
|
||||
#. The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router.
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:64
|
||||
msgid "The %s protocols/service enable permitted devices on local networks to autonomously set up IPv4/IPv6 port maps/forwards on this router."
|
||||
msgstr "%s 协议与服务允许局域网内获准的设备在此路由器上自动配置 IPv4/IPv6 端口映射与转发规则。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:67
|
||||
msgid "No suitable configuration was found!"
|
||||
msgstr "未找到适用的配置文件!"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:68
|
||||
msgid "No suitable config (LuCI app %s) found in %s. A related package update (daemon or LuCI app) may be missing."
|
||||
msgstr "在 %2$s 中未找到适用于当前前端(LuCI 应用 %1$s)的配置,可能缺少相关软件包(后端守护进程或 LuCI 应用)更新。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:69
|
||||
msgid "Use the software package manager, update lists, and install the related update. Config is migrated with the daemon package update."
|
||||
msgstr "请前往软件包管理器刷新列表并安装相关更新。更新后端守护进程软件包时会自动迁移配置文件。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:70
|
||||
msgid "Go to package manager…"
|
||||
msgstr "前往软件包管理器…"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:112
|
||||
msgid "Active Port Maps"
|
||||
msgstr "活动端口映射"
|
||||
msgstr "已生效的端口映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/status/include/80_upnp.js
|
||||
msgid "Active UPnP IGD & PCP/NAT-PMP Port Maps"
|
||||
msgstr "活动的 UPnP IGD & PCP/NAT-PMP 端口映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:116
|
||||
msgid "Service Settings"
|
||||
msgstr "服务设置"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:119
|
||||
msgid "Service Setup"
|
||||
msgstr "基本设置"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:120
|
||||
msgid "Access Control"
|
||||
msgstr "访问控制"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:121
|
||||
msgid "Advanced Settings"
|
||||
msgstr "高级设置"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:122
|
||||
msgid "UPnP IGD Adjustments"
|
||||
msgstr "UPnP IGD 调整"
|
||||
msgstr "UPnP IGD 参数调整"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:124
|
||||
msgid "Enable service"
|
||||
msgstr "启用服务"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:125
|
||||
msgid "Enable the autonomous port mapping service"
|
||||
msgstr "启用自动端口映射服务"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:128
|
||||
msgid "Enable protocols"
|
||||
msgstr "启用协议"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:129
|
||||
msgid "All protocols"
|
||||
msgstr "所有协议"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:130
|
||||
msgid "UPnP IGD"
|
||||
msgstr "UPnP IGD"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:131
|
||||
msgid "PCP and NAT-PMP"
|
||||
msgstr "PCP 和 NAT-PMP"
|
||||
msgstr "PCP 与 NAT-PMP"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:135
|
||||
msgid "Enable networks"
|
||||
msgstr "启用网络"
|
||||
msgstr "启用网络接口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:136
|
||||
msgid "Select local/internal (LAN) network interfaces to enable the service for"
|
||||
msgstr "选择要启用此服务的本地/内部(LAN)网络接口"
|
||||
msgstr "选择要启用此服务的本地内网(LAN)接口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:146
|
||||
msgid "UPnP IGD compatibility"
|
||||
msgstr "UPnP IGD 兼容性"
|
||||
msgstr "UPnP IGD 兼容模式"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:147
|
||||
msgid "Set compatibility mode (act as device) to workaround IGDv2-incompatible clients; %s are known to only work with %s (or) <br />Emulate/report a specific/different device to workaround/support/handle/bypass/assist/mitigate... (Alternative text welcome)"
|
||||
msgstr "设置兼容模式(作为特定设备运行)以兼容不支持 IGDv2 的客户端;已知 %s 仅适用于 %s"
|
||||
msgstr "设置协议兼容模式以适配不支持 IGDv2 的客户端;已知 %s 仅在 %s 模式下正常工作"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:149
|
||||
msgid "IGDv1 (IPv4 only)"
|
||||
msgstr "IGDv1(仅 IPv4)"
|
||||
msgstr "IGDv1(仅限 IPv4)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:150
|
||||
msgid "IGDv2 (with workarounds)"
|
||||
msgstr "IGDv2(含兼容处理)"
|
||||
msgstr "IGDv2(启用兼容修正)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:155
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:173
|
||||
msgid "Access defaults"
|
||||
msgstr "默认访问策略"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:156
|
||||
msgid "Set access control defaults for ports that all devices can map"
|
||||
msgstr "设置所有设备均可映射的端口默认访问控制策略"
|
||||
msgstr "设置默认允许所有内网设备映射的端口范围"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:157
|
||||
msgid "None / accept extra ports only"
|
||||
msgstr "无 / 仅允许额外指定端口"
|
||||
msgstr "无(仅允许下方额外指定的端口)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:158
|
||||
msgid "Accept ports >= 1024"
|
||||
msgstr "允许端口 >= 1024"
|
||||
msgstr "允许 1024 及以上端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:159
|
||||
msgid "Accept HTTP/HTTPS + ports >= 1024"
|
||||
msgstr "允许 HTTP/HTTPS + 端口 >= 1024"
|
||||
msgstr "允许 HTTP/HTTPS 及 1024 以上端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:160
|
||||
msgid "Accept HTTP/HTTPS ports"
|
||||
msgstr "允许 HTTP/HTTPS 端口"
|
||||
msgstr "仅允许 HTTP/HTTPS 端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:161
|
||||
msgid "Accept all ports"
|
||||
msgstr "允许所有端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:163
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:173
|
||||
msgid "Accept extra ports"
|
||||
msgstr "允许额外端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:166
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:173
|
||||
msgid "Reject ports"
|
||||
msgstr "拒绝端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:167
|
||||
msgid "Reject unsafe/insecure/risky FTP/Telnet/DCE/NetBIOS/SMB/RDP ports by default; overrides other settings; use %s for none"
|
||||
msgstr "默认拒绝不安全的 FTP/Telnet/DCE/NetBIOS/SMB/RDP 端口;此项优先于其他设置;填 %s 表示不拒绝任何端口"
|
||||
msgstr "默认拒绝存在安全风险的 FTP/Telnet/DCE/NetBIOS/SMB/RDP 等高危端口;优先级高于其他设置;若不限制请填 %s"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:171
|
||||
msgid "Check ACL"
|
||||
msgstr "检查访问控制列表(ACL)"
|
||||
msgstr "启用 ACL 检查"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:172
|
||||
msgid "Extend or override access defaults by device-specific permissions using the access control list (ACL)"
|
||||
msgstr "使用访问控制列表(ACL)按设备权限扩展或覆盖默认访问策略"
|
||||
msgstr "使用访问控制列表(ACL)按设备细分权限,以扩展或覆盖默认访问策略"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:173
|
||||
msgid "Sequence:"
|
||||
msgstr "匹配顺序:"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:173
|
||||
msgid "ACL entries (if checked)"
|
||||
msgstr "ACL 条目(若已勾选)"
|
||||
msgstr "ACL 条目(若启用)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:179
|
||||
msgid "Disable IPv6 mapping"
|
||||
msgstr "禁用 IPv6 映射"
|
||||
msgstr "禁用 IPv6 端口映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:180
|
||||
msgid "IPv6 is currently always accepted unless disabled"
|
||||
msgstr "除非禁用,否则当前默认始终允许 IPv6 映射"
|
||||
msgstr "除非勾选禁用,否则当前默认允许所有 IPv6 映射请求"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgctxt "Allow %s/%s (%s = CGNAT, %s = STUN)"
|
||||
#. Allow %s/%s (%s = CGNAT, %s = STUN)
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:183
|
||||
msgid "Allow %s/%s"
|
||||
msgstr "允许 %s/%s"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:186
|
||||
msgid "Allow use of unrestricted endpoint-independent (1:1) CGNATs and detect the public IPv4"
|
||||
msgstr "允许在无限制端点无关型(1:1)CGNAT 环境下使用并检测公网 IPv4"
|
||||
msgstr "允许在无限制的端点无关型(Full Cone / 1:1)CGNAT 网络下运行,并通过 STUN 探测公网 IPv4 地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:187
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:190
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:207
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:358
|
||||
msgid "Disabled"
|
||||
msgstr "禁用"
|
||||
msgstr "已禁用"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:187
|
||||
msgid "Manually override external IPv4 with public"
|
||||
msgstr "手动指定外部公网 IPv4 地址"
|
||||
msgstr "可手动指定公网 IPv4 地址以覆盖外部地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:188
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:189
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:208
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:209
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:210
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
msgstr "已启用"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:188
|
||||
msgid "Filtering test currently requires an extra firewall rule"
|
||||
msgstr "过滤测试目前需要额外的防火墙规则"
|
||||
msgstr "NAT 过滤类型检测目前需要额外配置防火墙规则"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:189
|
||||
msgid "allow filtered"
|
||||
msgstr "允许过滤型"
|
||||
msgstr "忽略过滤检测"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:189
|
||||
msgid "Workaround filtered IPv4 CGNAT test result"
|
||||
msgstr "绕过过滤型 IPv4 CGNAT 检测限制"
|
||||
msgstr "跳过或忽略受限型 IPv4 CGNAT 的过滤检测结果"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:190
|
||||
msgid "report private IPv4, avoid"
|
||||
msgstr "上报私有 IPv4,不推荐"
|
||||
msgstr "直接通告私网 IPv4,不推荐"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:190
|
||||
msgid "No STUN public IPv4 detection; client issues"
|
||||
msgstr "不通过 STUN 检测公网 IPv4;可能导致客户端异常"
|
||||
msgstr "不通过 STUN 探测公网 IPv4;可能导致客户端连接异常"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:193
|
||||
msgid "STUN server"
|
||||
msgstr "STUN 服务器"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:200
|
||||
msgid "Override external IPv4"
|
||||
msgstr "覆盖外部 IPv4"
|
||||
msgstr "指定外部 IPv4 地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:201
|
||||
msgid "Report custom public/external (WAN) IPv4 address"
|
||||
msgstr "通告自定义公网/外部(WAN)IPv4 地址"
|
||||
msgstr "向客户端通告自定义的公网/外部(WAN)IPv4 地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:205
|
||||
msgid "Allow third-party mapping"
|
||||
msgstr "允许第三方映射"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:206
|
||||
msgid "Allow adding port maps for non-requesting IP addresses; use with care"
|
||||
msgstr "允许为非请求方 IP 地址添加端口映射;请谨慎使用"
|
||||
msgstr "允许设备为其他内网 IP 地址代为添加端口映射;请谨慎启用"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:207
|
||||
msgid "recommended"
|
||||
msgstr "推荐"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:209
|
||||
msgid "UPnP IGD only"
|
||||
msgstr "仅 UPnP IGD"
|
||||
msgstr "仅限 UPnP IGD"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:210
|
||||
msgid "PCP only"
|
||||
msgstr "仅 PCP"
|
||||
msgstr "仅限 PCP"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:212
|
||||
msgid "Report system instead of service uptime"
|
||||
msgstr "报告系统运行时间而非服务运行时间"
|
||||
msgstr "通告系统运行时间而非服务运行时间"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:217
|
||||
msgid "Log level"
|
||||
msgstr "日志级别"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:218
|
||||
msgid "Default"
|
||||
msgstr "默认"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:219
|
||||
msgid "Info"
|
||||
msgstr "信息"
|
||||
msgstr "信息 (Info)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:220
|
||||
msgid "Debug"
|
||||
msgstr "调试"
|
||||
msgstr "调试 (Debug)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:224
|
||||
msgid "Service lease file"
|
||||
msgstr "服务租约文件"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:228
|
||||
msgid "Download speed"
|
||||
msgstr "下载速度"
|
||||
msgstr "下行速率"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:229
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:237
|
||||
msgid "Report maximum connection speed in kbit/s"
|
||||
msgstr "报告最大连接速度(kbit/s)"
|
||||
msgstr "向客户端通告的最大连接速率(单位:kbit/s)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:231
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:239
|
||||
msgid "Default interface link speed"
|
||||
msgstr "默认接口链路速度"
|
||||
msgstr "默认使用接口协商速率"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:236
|
||||
msgid "Upload speed"
|
||||
msgstr "上传速度"
|
||||
msgstr "上行速率"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:244
|
||||
msgid "Router/friendly name"
|
||||
msgstr "路由器/友好名称"
|
||||
msgstr "路由器友好名称"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:250
|
||||
msgid "Announced model number"
|
||||
msgstr "通告的型号"
|
||||
msgstr "通告设备型号"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:256
|
||||
msgid "Announced serial number"
|
||||
msgstr "通告的序列号"
|
||||
msgstr "通告序列号"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:262
|
||||
msgid "Router/presentation URL"
|
||||
msgstr "路由器/管理页面 URL"
|
||||
msgstr "路由器管理页面 URL"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:263
|
||||
msgid "Report custom router web interface URL"
|
||||
msgstr "通告自定义路由器 Web 管理界面 URL"
|
||||
msgstr "向客户端通告自定义的路由器 Web 管理界面地址"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:269
|
||||
msgid "Device UUID"
|
||||
msgstr "设备 UUID"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:275
|
||||
msgid "SOAP/HTTP port"
|
||||
msgstr "SOAP/HTTP 端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:282
|
||||
msgid "Notify interval"
|
||||
msgstr "通知间隔"
|
||||
msgstr "通告间隔"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
msgctxt "A 900 s interval sends %s (%s = SSDP) announcements with the minimum %s (%s = Cache-Control: max-age=1800) header"
|
||||
#. A 900 s interval sends %s (%s = SSDP) announcements with the minimum %s (%s = Cache-Control: max-age=1800) header
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:283
|
||||
msgid "A 900 s interval sends %s announcements with the minimum %s header"
|
||||
msgstr "900 秒的间隔将发送 %s 通告,并附带最小 %s 标头"
|
||||
msgstr "设为 900 秒间隔时,将以最小有效期标头(%2$s)发送 %1$s 服务通告"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:294
|
||||
msgid "Access Control List"
|
||||
msgstr "访问控制列表"
|
||||
msgstr "访问控制列表 (ACL)"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:295
|
||||
msgid "The access control list (ACL) specifies which IPv4 addresses and ports can be mapped."
|
||||
msgstr "访问控制列表(ACL)用于指定允许映射的 IPv4 地址和端口。"
|
||||
msgstr "访问控制列表(ACL)用于指定允许或禁止哪些内网 IPv4 地址及端口进行映射。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:296
|
||||
msgid "ACL entries are checked in order, then rejected if not matched and not accepted by access defaults. (To do: should be part of access control tab)"
|
||||
msgstr "ACL 条目按顺序进行匹配;若未匹配且未被默认访问策略允许,则拒绝映射。"
|
||||
msgstr "ACL 条目按顺序依次匹配;若均未命中且不符合默认访问策略,则拒绝该请求。"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:301
|
||||
msgid "UPnP IGD & PCP/NAT-PMP"
|
||||
msgstr "UPnP IGD & PCP/NAT-PMP"
|
||||
msgstr "UPnP IGD 与 PCP/NAT-PMP"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:301
|
||||
msgid "Edit ACL Entry"
|
||||
msgstr "编辑 ACL 条目"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:319
|
||||
msgid "Comment"
|
||||
msgstr "备注"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:320
|
||||
msgid "unspecified"
|
||||
msgstr "未指定"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:331
|
||||
msgid "Enter device's IPv4 address, or an address and netmask"
|
||||
msgstr "输入设备的 IPv4 地址,或带子网掩码的地址"
|
||||
msgstr "输入设备的 IPv4 地址,或输入 IPv4 网段与子网掩码"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:340
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:347
|
||||
msgid "any port"
|
||||
msgstr "任意端口"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:351
|
||||
msgid "Description filter"
|
||||
msgstr "描述过滤器"
|
||||
msgstr "描述过滤规则"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:352
|
||||
msgid "Regular expression to check description of UPnP IGD IPv4 port maps"
|
||||
msgstr "用于匹配 UPnP IGD IPv4 端口映射描述的正则表达式"
|
||||
msgstr "用于匹配和检查 UPnP IGD IPv4 端口映射描述的正则表达式"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:353
|
||||
msgid "any description"
|
||||
msgstr "任意描述"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:355
|
||||
msgid "Action"
|
||||
msgstr "动作"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:356
|
||||
msgid "Accept"
|
||||
msgstr "允许"
|
||||
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js
|
||||
#: applications/luci-app-upnp/htdocs/luci-static/resources/view/upnp/upnp.js:357
|
||||
msgid "Reject"
|
||||
msgstr "拒绝"
|
||||
|
||||
#: applications/luci-app-upnp/root/usr/share/rpcd/acl.d/luci-app-upnp.json:3
|
||||
msgid "Grant access to UPnP IGD & PCP"
|
||||
msgstr "授予访问 UPnP IGD 与 PCP 的权限"
|
||||
@@ -111,7 +111,6 @@ return view.extend({
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'v2ray_assetsdir', _('Rule data directory'));
|
||||
o.placeholder = '/usr/share/v2raya';
|
||||
o.default = '/usr/share/v2raya';
|
||||
o.description = _('Directory containing geoip.dat and geosite.dat. Leave empty for auto-detection.');
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'v2ray_bin', _('Core binary (v2raya_core only)'));
|
||||
|
||||
@@ -123,13 +123,23 @@ function runAction(action, button) {
|
||||
}
|
||||
|
||||
function startService(button) {
|
||||
uci.set('v2raya', 'config', 'enabled', '1');
|
||||
setBusy(button, true);
|
||||
|
||||
return L.resolveDefault(uci.save(), null).then(function () {
|
||||
/* Commit before starting: the init script reads enabled from UCI. */
|
||||
return L.resolveDefault(uci.apply(), null);
|
||||
}).then(function () {
|
||||
return runAction('start', button);
|
||||
return fs.exec('/usr/libexec/luci-app-v2raya/enable', []).then(function (res) {
|
||||
if (!res || res.code !== 0) {
|
||||
setBusy(button, false);
|
||||
ui.addNotification(null, E('p', {},
|
||||
(res && (res.stderr || res.stdout)) || _('Could not enable the service.')), 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
uci.unload('v2raya');
|
||||
return uci.load('v2raya').then(function () {
|
||||
return runAction('start', button);
|
||||
});
|
||||
}).catch(function (err) {
|
||||
setBusy(button, false);
|
||||
ui.addNotification(null, E('p', {}, _('Could not enable the service: %s').format(err)), 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -0,0 +1,366 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Project-Id-Version: luci-app-v2raya\n"
|
||||
"Language: zh_Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json
|
||||
msgid "v2rayA"
|
||||
msgstr "v2rayA"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "概览"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "设置"
|
||||
|
||||
msgid "Log"
|
||||
msgstr "日志"
|
||||
|
||||
#: luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js
|
||||
msgid "A web client for its bundled v2raya_core, with transparent proxy support."
|
||||
msgstr "专为内置 v2raya_core 打造的 Web 客户端,支持透明代理。"
|
||||
|
||||
msgid "Service"
|
||||
msgstr "运行状态"
|
||||
|
||||
msgid "Checking…"
|
||||
msgstr "正在检查…"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr "已停止"
|
||||
|
||||
msgid "v2rayA version"
|
||||
msgstr "v2rayA 版本"
|
||||
|
||||
msgid "v2raya_core version"
|
||||
msgstr "v2raya_core 版本"
|
||||
|
||||
msgid "not detected"
|
||||
msgstr "未检测到"
|
||||
|
||||
msgid "Listening address"
|
||||
msgstr "监听地址"
|
||||
|
||||
msgid "Web interface"
|
||||
msgstr "Web 管理界面"
|
||||
|
||||
msgid "Open v2rayA"
|
||||
msgstr "打开 v2rayA"
|
||||
|
||||
msgid "Detected cores and proxy services"
|
||||
msgstr "已检测到的内核与代理服务"
|
||||
|
||||
msgid "v2rayA 2.5.x runs only its bundled v2raya_core. Existing Xray-core, v2ray and sing-box installations are detected here so they are not duplicated or replaced; they are never used as the v2rayA backend."
|
||||
msgstr "v2rayA 2.5.x 仅运行其内置的 v2raya_core。此处检测系统中已有的 Xray-core、v2ray 和 sing-box 仅用于避免重复安装或覆盖,它们绝不会被用作 v2rayA 的后端内核。"
|
||||
|
||||
msgid "v2raya_core (required)"
|
||||
msgstr "v2raya_core(必需)"
|
||||
|
||||
msgid "This is the only core v2rayA 2.5.x supports."
|
||||
msgstr "这是 v2rayA 2.5.x 唯一支持的内核。"
|
||||
|
||||
msgid "Xray-core"
|
||||
msgstr "Xray-core"
|
||||
|
||||
msgid "Left untouched. Do not point the core binary setting at this."
|
||||
msgstr "保持原样,请勿将核心二进制文件路径指向此处。"
|
||||
|
||||
msgid "v2ray-core"
|
||||
msgstr "v2ray-core"
|
||||
|
||||
msgid "sing-box"
|
||||
msgstr "sing-box"
|
||||
|
||||
msgid "Left untouched. sing-box uses a different configuration format and cannot run v2rayA configs."
|
||||
msgstr "保持原样。sing-box 使用不同的配置格式,无法运行 v2rayA 生成的配置。"
|
||||
|
||||
msgid "Detected"
|
||||
msgstr "已检测到"
|
||||
|
||||
msgid "Not installed"
|
||||
msgstr "未安装"
|
||||
|
||||
msgid "Package installed"
|
||||
msgstr "已通过软件包安装"
|
||||
|
||||
msgid "Rule data"
|
||||
msgstr "规则数据"
|
||||
|
||||
msgid "geoip.dat and geosite.dat are installed"
|
||||
msgstr "已安装 geoip.dat 和 geosite.dat"
|
||||
|
||||
msgid "Missing. v2rayA downloads the data on first start and needs internet access; install v2raya-geoip and v2raya-geosite for offline use."
|
||||
msgstr "缺失。v2rayA 首次启动时需联网自动下载这些数据;如需离线使用,请提前安装 v2raya-geoip 和 v2raya-geosite 软件包。"
|
||||
|
||||
msgid "Other proxy services"
|
||||
msgstr "其他代理服务"
|
||||
|
||||
msgid "None detected"
|
||||
msgstr "未检测到"
|
||||
|
||||
msgid "Core and service detection is unavailable. The installed v2raya package does not provide the detection helper; version 2.5.x and later install it."
|
||||
msgstr "内核与服务检测不可用。当前安装的 v2raya 软件包未提供检测辅助工具(2.5.x 及更高版本已内置)。"
|
||||
|
||||
msgid "v2raya_core is missing. Install the v2raya-core package."
|
||||
msgstr "缺少 v2raya_core,请安装 v2raya-core 软件包。"
|
||||
|
||||
msgid "An older v2raya package is still installed. Remove it to avoid duplicate services."
|
||||
msgstr "系统中仍安装有旧版 v2raya 软件包,请将其卸载以避免服务冲突。"
|
||||
|
||||
msgid "These proxy services are enabled at boot: %s. Run only one transparent-proxy stack, otherwise duplicate firewall rules will conflict."
|
||||
msgstr "以下代理服务已设为开机自启:%s。请仅运行一套透明代理方案,否则重复的防火墙规则将产生冲突。"
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "服务控制"
|
||||
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "重启"
|
||||
|
||||
msgid "These buttons control the background service. Connecting the proxy is done in the v2rayA web UI with the Connect button."
|
||||
msgstr "以上按钮仅用于控制后台服务进程;如需连接节点并开启代理,请进入 v2rayA Web 管理界面点击连接按钮(Connect)。"
|
||||
|
||||
msgid "This independent, community-run packaging project is not affiliated with the upstream v2rayA authors. It is AI-assisted and human-maintained."
|
||||
msgstr "本软件包为独立的社区维护项目,与上游 v2rayA 官方作者无关联。项目由 AI 辅助构建并由人工维护。"
|
||||
|
||||
msgid "The service command failed."
|
||||
msgstr "服务命令执行失败。"
|
||||
|
||||
msgid "Service action \"%s\" completed."
|
||||
msgstr "服务操作“%s”已完成。"
|
||||
|
||||
msgid "Unable to run the service command: %s"
|
||||
msgstr "无法执行服务命令:%s"
|
||||
|
||||
msgid "Could not enable the service."
|
||||
msgstr "无法启用服务。"
|
||||
|
||||
msgid "Could not enable the service: %s"
|
||||
msgstr "无法启用服务:%s"
|
||||
|
||||
#: luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js
|
||||
msgid "Settings for the v2rayA service. Save & Apply restarts the service so the new values take effect. v2rayA 2.5.x uses only its bundled v2raya_core; Xray-core and sing-box are not supported backends."
|
||||
msgstr "v2rayA 服务参数设置。“保存并应用”后将自动重启服务以使新配置生效。v2rayA 2.5.x 仅支持其内置的 v2raya_core,不支持将 Xray-core 或 sing-box 作为后端内核。"
|
||||
|
||||
msgid "General"
|
||||
msgstr "常规设置"
|
||||
|
||||
msgid "Transparent proxy"
|
||||
msgstr "透明代理"
|
||||
|
||||
msgid "The transparent proxy mode, routing rules and nodes are configured in the v2rayA web interface. These options control core start-up and hooks."
|
||||
msgstr "透明代理模式、分流规则及代理节点均在 v2rayA Web 管理界面中配置。此处的选项用于控制内核启动参数及生命周期钩子脚本。"
|
||||
|
||||
msgid "Logging"
|
||||
msgstr "日志设置"
|
||||
|
||||
msgid "Advanced"
|
||||
msgstr "高级设置"
|
||||
|
||||
msgid "Enable service"
|
||||
msgstr "启用服务"
|
||||
|
||||
msgid "Start v2rayA at boot. You can also start or stop it from the overview page."
|
||||
msgstr "开机自动启动 v2rayA 服务。你也可以在“概览”页面手动启动或停止服务。"
|
||||
|
||||
msgid "An address is required."
|
||||
msgstr "必须填写监听地址。"
|
||||
|
||||
msgid "Enter an address and port, for example 0.0.0.0:2017 or [::]:2017."
|
||||
msgstr "请输入有效的地址和端口,例如 0.0.0.0:2017 或 [::]:2017。"
|
||||
|
||||
msgid "The web interface listens on this address. Use 127.0.0.1:2017 to keep it off the LAN."
|
||||
msgstr "Web 管理界面监听的 IP 地址与端口。若设为 127.0.0.1:2017,则仅允许本机访问(禁止局域网其他设备访问)。"
|
||||
|
||||
msgid "Base URL"
|
||||
msgstr "基础路径 (Base URL)"
|
||||
|
||||
msgid "URL prefix when v2rayA runs behind a reverse proxy."
|
||||
msgstr "当 v2rayA 运行在反向代理后端时使用的 URL 前缀。"
|
||||
|
||||
msgid "The base URL must start with /."
|
||||
msgstr "基础路径必须以 / 开头。"
|
||||
|
||||
msgid "IPv6 support"
|
||||
msgstr "IPv6 支持"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "自动"
|
||||
|
||||
msgid "On"
|
||||
msgstr "开启"
|
||||
|
||||
msgid "Off"
|
||||
msgstr "关闭"
|
||||
|
||||
msgid "Make sure IPv6 connectivity works before forcing it on."
|
||||
msgstr "强制开启前,请确保路由器及网络环境已具备正常的 IPv6 连通性。"
|
||||
|
||||
msgid "nftables support"
|
||||
msgstr "nftables 支持"
|
||||
|
||||
msgid "nftables is the default firewall backend on OpenWrt 22.03 and later."
|
||||
msgstr "nftables 是 OpenWrt 22.03 及更高版本的默认防火墙后端。"
|
||||
|
||||
msgid "Core startup timeout"
|
||||
msgstr "内核启动超时"
|
||||
|
||||
msgid "Seconds to wait for v2raya_core to start. Raise this on slow MIPS routers."
|
||||
msgstr "等待 v2raya_core 启动的最长时间(秒)。性能较弱的 MIPS 架构路由器可适当调高此值。"
|
||||
|
||||
msgid "Respect bound devices in redirect mode"
|
||||
msgstr "Redirect 模式下放行绑定网卡的流量"
|
||||
|
||||
msgid "Let sockets bound to a device bypass the proxy. Requires Linux 5.14 or later and cgroup v2."
|
||||
msgstr "允许已绑定到特定网络接口(SO_BINDTODEVICE)的套接字绕过透明代理。需要 Linux 5.14 及以上内核并启用 cgroup v2。"
|
||||
|
||||
msgid "Transparent proxy hook"
|
||||
msgstr "透明代理钩子脚本"
|
||||
|
||||
msgid "Executable called around transparent proxy setup and teardown."
|
||||
msgstr "在配置或清理透明代理规则前后调用的可执行脚本路径。"
|
||||
|
||||
msgid "Core life-cycle hook"
|
||||
msgstr "内核生命周期钩子脚本"
|
||||
|
||||
msgid "Executable called around v2raya_core start and stop."
|
||||
msgstr "在启动或停止 v2raya_core 前后调用的可执行脚本路径。"
|
||||
|
||||
msgid "Plugin manager"
|
||||
msgstr "插件管理器"
|
||||
|
||||
msgid "Executable used to parse protocols that v2rayA does not know natively."
|
||||
msgstr "用于解析 v2rayA 原生不支持的代理协议的可执行文件路径。"
|
||||
|
||||
msgid "Log level"
|
||||
msgstr "日志级别"
|
||||
|
||||
msgid "Trace"
|
||||
msgstr "追踪 (Trace)"
|
||||
|
||||
msgid "Debug"
|
||||
msgstr "调试 (Debug)"
|
||||
|
||||
msgid "Info"
|
||||
msgstr "信息 (Info)"
|
||||
|
||||
msgid "Warning"
|
||||
msgstr "警告 (Warning)"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "错误 (Error)"
|
||||
|
||||
msgid "Warning keeps flash writes down. Use Info while debugging."
|
||||
msgstr "设为“警告 (Warning)”可减少对路由器闪存的写入损耗;排查故障时可临时改为“信息 (Info)”。"
|
||||
|
||||
msgid "Keep logs for"
|
||||
msgstr "日志保留天数"
|
||||
|
||||
msgid "Number of days to keep rotated log files."
|
||||
msgstr "轮转日志文件的保留天数。"
|
||||
|
||||
msgid "Disable colours in the log"
|
||||
msgstr "禁用日志颜色代码"
|
||||
|
||||
msgid "The log file is plain text, so colours are not useful there."
|
||||
msgstr "日志文件为纯文本格式,禁用 ANSI 颜色代码可避免出现乱码字符。"
|
||||
|
||||
msgid "Disable timestamps in the log"
|
||||
msgstr "禁用日志时间戳"
|
||||
|
||||
msgid "Only relevant when v2rayA writes to syslog or journald."
|
||||
msgstr "仅在 v2rayA 将日志输出至系统日志(syslog 或 journald)时生效。"
|
||||
|
||||
msgid "Rule data directory"
|
||||
msgstr "规则数据目录"
|
||||
|
||||
msgid "Directory containing geoip.dat and geosite.dat. Leave empty for auto-detection."
|
||||
msgstr "存放 geoip.dat 和 geosite.dat 规则文件的目录路径,留空则自动检测。"
|
||||
|
||||
msgid "Core binary (v2raya_core only)"
|
||||
msgstr "内核二进制文件(仅限 v2raya_core)"
|
||||
|
||||
msgid "Auto-detect"
|
||||
msgstr "自动检测"
|
||||
|
||||
msgid "Path to v2raya_core. Leave empty to use the binary next to v2raya or in PATH. Only the matching v2raya_core is supported; Xray-core and sing-box use incompatible configuration formats."
|
||||
msgstr "v2raya_core 可执行文件的路径。留空则自动使用与 v2raya 同目录或系统 PATH 中的程序。仅支持版本匹配的 v2raya_core,Xray-core 和 sing-box 因配置格式不兼容而无法使用。"
|
||||
|
||||
msgid "Extra configuration directory"
|
||||
msgstr "额外配置目录"
|
||||
|
||||
msgid "None"
|
||||
msgstr "无"
|
||||
|
||||
msgid "Additional v2ray configuration files merged into the generated configuration."
|
||||
msgstr "存放额外 V2Ray 配置文件的目录,其中的配置将合并到自动生成的最终配置中。"
|
||||
|
||||
msgid "Go memory limit"
|
||||
msgstr "Go 内存限制 (GOMEMLIMIT)"
|
||||
|
||||
msgid "Soft memory limit for both v2raya and v2raya_core (GOMEMLIMIT). Use 0 to disable, or 96MiB on a larger router."
|
||||
msgstr "v2raya 与 v2raya_core 各自的 Go 运行时软内存上限(GOMEMLIMIT)。设为 0 表示不限制,大内存路由器可设为 96MiB。"
|
||||
|
||||
msgid "Go scheduler threads"
|
||||
msgstr "Go 调度线程数 (GOMAXPROCS)"
|
||||
|
||||
msgid "GOMAXPROCS. 0 uses every CPU core; 2 is enough for most routers."
|
||||
msgstr "设置 Go 运行时最大并发线程数(GOMAXPROCS)。设为 0 表示使用全部 CPU 核心;大多数路由器保持默认 2 即可。"
|
||||
|
||||
msgid "Go GC percent"
|
||||
msgstr "Go 垃圾回收阈值 (GOGC)"
|
||||
|
||||
msgid "GOGC. 0 keeps the Go default; lower values use less memory and more CPU."
|
||||
msgstr "设置 Go 垃圾回收触发百分比(GOGC)。设为 0 表示保持 Go 默认值;调低数值可降低内存占用,但会增加 CPU 开销。"
|
||||
|
||||
msgid "Open file limit"
|
||||
msgstr "最大打开文件数 (nofile)"
|
||||
|
||||
msgid "Maximum open file descriptors. 16384 is plenty for a home router and bounds a connection flood."
|
||||
msgstr "进程允许打开的最大文件描述符数量(ulimit -n)。16384 足以满足家用路由器需求,并能防止异常连接洪水耗尽资源。"
|
||||
|
||||
#: luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js
|
||||
msgid "v2rayA log"
|
||||
msgstr "v2rayA 运行日志"
|
||||
|
||||
msgid "Showing the last %d lines of %s. Refreshes every %d seconds."
|
||||
msgstr "显示最新 %d 行日志(%s),每 %d 秒自动刷新。"
|
||||
|
||||
msgid "Loading…"
|
||||
msgstr "正在加载…"
|
||||
|
||||
msgid "The log file does not exist yet. Start the service and try again."
|
||||
msgstr "日志文件尚未生成,请先启动服务后再试。"
|
||||
|
||||
msgid "The log is empty."
|
||||
msgstr "日志为空。"
|
||||
|
||||
msgid "Unable to read the log: %s"
|
||||
msgstr "无法读取日志:%s"
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr "刷新"
|
||||
|
||||
msgid "Clear log"
|
||||
msgstr "清空日志"
|
||||
|
||||
msgid "Clear the v2rayA log file?"
|
||||
msgstr "确定要清空 v2rayA 日志文件吗?"
|
||||
|
||||
msgid "Unable to clear the log file."
|
||||
msgstr "无法清空日志文件。"
|
||||
|
||||
msgid "Log file cleared."
|
||||
msgstr "日志文件已清空。"
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||
#
|
||||
# Commit only the enabled option for the v2rayA service. The LuCI overview
|
||||
# calls this before starting the service; uci.apply() would commit every
|
||||
# staged change in the session.
|
||||
|
||||
set -eu
|
||||
|
||||
uci -q set v2raya.config.enabled='1'
|
||||
uci -q commit v2raya
|
||||
@@ -3,8 +3,8 @@
|
||||
"description": "Grant access to the v2rayA service and configuration",
|
||||
"read": {
|
||||
"file": {
|
||||
"/usr/bin/v2raya": [ "exec" ],
|
||||
"/usr/bin/v2raya_core": [ "exec" ],
|
||||
"/usr/bin/v2raya --version": [ "exec" ],
|
||||
"/usr/bin/v2raya_core version": [ "exec" ],
|
||||
"/usr/libexec/v2raya-detect": [ "exec" ],
|
||||
"/var/log/v2raya/v2raya.log": [ "read" ]
|
||||
},
|
||||
@@ -18,6 +18,7 @@
|
||||
"/etc/init.d/v2raya start": [ "exec" ],
|
||||
"/etc/init.d/v2raya stop": [ "exec" ],
|
||||
"/etc/init.d/v2raya restart": [ "exec" ],
|
||||
"/usr/libexec/luci-app-v2raya/enable": [ "exec" ],
|
||||
"/var/log/v2raya/v2raya.log": [ "write" ]
|
||||
},
|
||||
"uci": [ "v2raya" ]
|
||||
|
||||
@@ -12,8 +12,8 @@ LUCI_TITLE:=Argon Theme
|
||||
LUCI_DEPENDS:=+luci-base +wget-ssl +ca-bundle +jsonfilter +ucode-mod-math +flock +coreutils +coreutils-timeout
|
||||
# Keep existing translation package names and the argon-config message domain.
|
||||
LUCI_BASENAME:=argon-config
|
||||
PKG_VERSION:=3.1.3
|
||||
PKG_RELEASE:=42
|
||||
PKG_VERSION:=3.1.4
|
||||
PKG_RELEASE:=43
|
||||
PKG_LICENSE:=Apache-2.0 MIT
|
||||
|
||||
# LuCI derives translations independently of the theme version. Keep release
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Update log for master branch
|
||||
|
||||
## v3.1.4 [ 2026.11.13 ]
|
||||
|
||||
- Fix inline alerts obscuring dropdowns and clipped dropdown options in tables (#740, #741).
|
||||
- Support newer LuCI dashboard layouts and configurable widget interactions (#742).
|
||||
- Improve Firefox ESR appearance controls, joined inputs, dropdowns and port statistics (#739).
|
||||
- Add background video playback controls and a keyboard skip link, and reduce realtime chart layout work.
|
||||
|
||||
## v3.1.3 [ 2026.11.06 ]
|
||||
|
||||
- Preserve bundled Argon translations during upgrades and synchronize language packages in subsequent online updates (#737).
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Master 分支的更新日志
|
||||
|
||||
## v3.1.4 [ 2026.11.13 ]
|
||||
|
||||
- 修复提示条遮挡下拉菜单、表格下拉选项显示不完整的问题(#740、#741)。
|
||||
- 适配新版 LuCI 仪表盘,修复布局及组件配置交互(#742)。
|
||||
- 改善 Firefox ESR 的主题切换、组合控件、下拉菜单及端口统计显示(#739)。
|
||||
- 增加视频背景播放/暂停与键盘跳转导航,优化实时图表更新性能。
|
||||
|
||||
## v3.1.3 [ 2026.11.06 ]
|
||||
|
||||
- 修复部分固件升级后 Argon 翻译丢失,保留已有翻译,并在后续在线升级时同步语言包(#737)。
|
||||
|
||||
@@ -144,3 +144,5 @@ UCODE_BIN=/path/to/ucode sh .github/scripts/check_theme.sh
|
||||
The script checks JavaScript, shell and Ucode syntax, verifies compiled CSS against LESS, and runs the Node/Python tests. CI's Ucode build is defined in [check.yml](../.github/workflows/check.yml).
|
||||
|
||||
For UI changes, check the actual affected LuCI pages in light/dark modes and desktop/mobile widths, including keyboard focus, long text and reduced motion where relevant. Network and hardware behavior still needs a suitable OpenWrt environment.
|
||||
|
||||
Include Firefox ESR 115, 128, 140 and 153 when checking login and theme settings. Leave browser feature preferences at their defaults. `argon-compat.js` supplies DOM markers for joined controls and dropdown table sections only when native `:has()` is unavailable; use forgiving `:is()` lists to keep those fallback selectors valid. Keep new browser API use feature-detected, and avoid unsupported selectors in JavaScript queries. The fallback covers common theme controls; plugin-specific layouts need their own checks.
|
||||
@@ -144,3 +144,5 @@ UCODE_BIN=/path/to/ucode sh .github/scripts/check_theme.sh
|
||||
脚本检查 JavaScript、Shell、Ucode 语法,核对 LESS 与 CSS 产物,并运行 Node / Python 测试。CI 的 Ucode 构建步骤见 [check.yml](../.github/workflows/check.yml)。
|
||||
|
||||
UI 改动应检查实际受影响页面的亮暗色、桌面与窄屏,以及相关的键盘焦点、长文本和减少动画场景。网络和硬件行为仍需相应 OpenWrt 环境验证。
|
||||
|
||||
登录和主题设置的检查应包含 Firefox ESR 115、128、140、153,保留浏览器默认的特性设置。`argon-compat.js` 仅在缺少原生 `:has()` 时为组合控件和下拉表格添加 DOM 标记,通过可容错的 `:is()` 选择器提供样式。新增浏览器 API 应检测能力,JavaScript 查询应避免不受支持的选择器。此兼容处理覆盖主题常用控件,插件专属布局仍需单独验证。
|
||||
@@ -510,6 +510,23 @@
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
z-index: 1200;
|
||||
inset-block-start: var(--control-gap);
|
||||
inset-inline-start: var(--page-gutter);
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--text-strong);
|
||||
background: var(--surface-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-control);
|
||||
transform: translateY(calc(-100% - var(--control-gap)));
|
||||
}
|
||||
.skip-link:focus {
|
||||
transform: none;
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.main-left,
|
||||
.main-right,
|
||||
@@ -1128,23 +1145,53 @@ select[multiple] {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.login-page .volume-control {
|
||||
.login-page .background-controls {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
inset-block-start: var(--field-gap);
|
||||
inset-inline-end: var(--field-gap);
|
||||
display: flex;
|
||||
gap: var(--control-gap);
|
||||
z-index: 5000;
|
||||
cursor: pointer;
|
||||
border: 0 !important;
|
||||
background: url(../img/volume_high.svg) no-repeat center / contain;
|
||||
}
|
||||
.login-page .volume-control:focus-visible {
|
||||
.login-page .background-controls > button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: var(--surface-panel);
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.login-page .background-controls > button::before {
|
||||
content: '';
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: currentColor;
|
||||
mask: var(--background-control-icon) center / contain no-repeat;
|
||||
-webkit-mask: var(--background-control-icon) center / contain no-repeat;
|
||||
}
|
||||
.login-page .background-controls > button:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 4px;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
.login-page .background-controls > button::before {
|
||||
forced-color-adjust: none;
|
||||
background-color: ButtonText;
|
||||
}
|
||||
}
|
||||
.login-page .playback-control {
|
||||
--background-control-icon: url(../icon/play.svg);
|
||||
}
|
||||
.login-page .playback-control.playing {
|
||||
--background-control-icon: url(../icon/pause.svg);
|
||||
}
|
||||
.login-page .volume-control {
|
||||
--background-control-icon: url(../img/volume_high.svg);
|
||||
}
|
||||
.login-page .volume-control.mute {
|
||||
background-image: url(../img/volume_off.svg);
|
||||
--background-control-icon: url(../img/volume_off.svg);
|
||||
}
|
||||
.login-page .login-container {
|
||||
--login-inset: 3.125rem;
|
||||
@@ -1795,7 +1842,7 @@ footer > a {
|
||||
background: var(--surface-color);
|
||||
box-shadow: var(--section-shadow);
|
||||
}
|
||||
#view > div:first-child:not(.cbi-map) {
|
||||
#view > div:first-child:not(.cbi-map, .Dashboard) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
@@ -1991,6 +2038,7 @@ span.spinning {
|
||||
.alert-message {
|
||||
transform: none;
|
||||
position: static;
|
||||
z-index: auto;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
.alert-message.fade-out {
|
||||
@@ -2316,6 +2364,10 @@ fieldset > fieldset,
|
||||
.cbi-tblsection {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cbi-tblsection:is(:has(.cbi-dropdown), .argon-dropdown-section),
|
||||
.cbi-tblsection .table:is(:has(.cbi-dropdown), .argon-dropdown-section) {
|
||||
overflow: visible;
|
||||
}
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
@@ -2865,18 +2917,11 @@ header .header-actions {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option:has(input:checked) {
|
||||
color: var(--dropdown-selected-text);
|
||||
background: var(--dropdown-selected-bg);
|
||||
}
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option:has(input:checked)::after {
|
||||
visibility: visible;
|
||||
}
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option:has(input:focus-visible) {
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option:focus-within {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option:has(input:disabled) {
|
||||
#argon-appearance .argon-appearance-panel .argon-appearance-option[aria-disabled="true"] {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
@@ -2894,7 +2939,7 @@ header .header-actions {
|
||||
#argon-appearance .argon-appearance-trigger:hover {
|
||||
background-color: var(--surface-active);
|
||||
}
|
||||
#argon-appearance .argon-appearance-option:has(input:not(:disabled)):not(:has(input:checked)):hover {
|
||||
#argon-appearance .argon-appearance-option:not([aria-disabled="true"]):hover {
|
||||
background: var(--dropdown-hover-bg);
|
||||
}
|
||||
}
|
||||
@@ -2914,6 +2959,17 @@ header .header-actions {
|
||||
background-color: CanvasText;
|
||||
}
|
||||
}
|
||||
#argon-appearance[data-mode="light"] .argon-appearance-option[data-mode="light"],
|
||||
#argon-appearance[data-mode="dark"] .argon-appearance-option[data-mode="dark"],
|
||||
#argon-appearance[data-mode="normal"] .argon-appearance-option[data-mode="normal"] {
|
||||
color: var(--dropdown-selected-text);
|
||||
background: var(--dropdown-selected-bg);
|
||||
}
|
||||
#argon-appearance[data-mode="light"] .argon-appearance-option[data-mode="light"]::after,
|
||||
#argon-appearance[data-mode="dark"] .argon-appearance-option[data-mode="dark"]::after,
|
||||
#argon-appearance[data-mode="normal"] .argon-appearance-option[data-mode="normal"]::after {
|
||||
visibility: visible;
|
||||
}
|
||||
@starting-style {
|
||||
#argon-appearance .argon-appearance-panel {
|
||||
opacity: 0;
|
||||
@@ -3216,11 +3272,11 @@ header .header-actions {
|
||||
.control-group > * {
|
||||
max-width: 100%;
|
||||
}
|
||||
.control-group:has(> input:first-child + .cbi-button) {
|
||||
.control-group:is(:has(> input:first-child + .cbi-button), .argon-joined-control) {
|
||||
flex-wrap: nowrap;
|
||||
gap: 0;
|
||||
}
|
||||
.control-group:has(> input:first-child + .cbi-button) > input {
|
||||
.control-group:is(:has(> input:first-child + .cbi-button), .argon-joined-control) > input {
|
||||
flex: 0 1 15.5rem;
|
||||
min-width: 0;
|
||||
border-inline-end-width: 0;
|
||||
@@ -3228,14 +3284,14 @@ header .header-actions {
|
||||
border-end-end-radius: 0;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
.control-group:has(> input:first-child + .cbi-button) > input + .cbi-button {
|
||||
.control-group:is(:has(> input:first-child + .cbi-button), .argon-joined-control) > input + .cbi-button {
|
||||
flex: 0 0 auto;
|
||||
border-inline-start-width: 0;
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
.control-group:has(> input:first-child + .cbi-button) > .cbi-input-password + .cbi-button {
|
||||
.control-group:is(:has(> input:first-child + .cbi-button), .argon-joined-control) > .cbi-input-password + .cbi-button {
|
||||
flex-basis: 2.5rem;
|
||||
padding-inline: 0;
|
||||
}
|
||||
@@ -6591,6 +6647,9 @@ pre.command-output {
|
||||
min-width: min(100%, 18rem);
|
||||
padding-block-end: var(--field-gap);
|
||||
}
|
||||
#view > .Dashboard .dashboard-charts > .cbi-section > h3:first-child {
|
||||
padding: var(--field-gap) var(--section-inset);
|
||||
}
|
||||
#view > .Dashboard .dashboard-charts > .cbi-section > :not(h3:first-child) {
|
||||
margin-inline: var(--section-inset);
|
||||
}
|
||||
@@ -6628,6 +6687,47 @@ pre.command-output {
|
||||
#view > .Dashboard [data-tab="layout"] .cbi-page-actions {
|
||||
padding: var(--field-gap) var(--section-inset);
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-list > .item {
|
||||
align-items: flex-start;
|
||||
pointer-events: auto;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-list > .item > span {
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-settings {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-control) 0 0 var(--radius-control);
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-settings > summary {
|
||||
box-sizing: border-box;
|
||||
min-height: calc(2.5rem - 2px);
|
||||
padding: 0.5rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
list-style-position: inside;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-settings > summary:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: -2px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
[dir="rtl"] #view > .Dashboard .dashboard-widget-settings {
|
||||
direction: rtl;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-options {
|
||||
margin: 0;
|
||||
padding: var(--control-gap) 0.75rem;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-options .cbi-checkbox {
|
||||
height: auto;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
#view > .Dashboard .dashboard-widget-options .cbi-checkbox + br {
|
||||
display: none;
|
||||
}
|
||||
[data-page="admin-status-processes"] #view > .table {
|
||||
min-width: 0;
|
||||
border-radius: var(--radius-card);
|
||||
@@ -6737,8 +6837,8 @@ pre.command-output {
|
||||
[data-page="admin-status-overview"] #view > div:first-child {
|
||||
gap: 0 !important;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]),
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid),
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 8.5rem), 1fr)) !important;
|
||||
align-items: stretch !important;
|
||||
@@ -6747,40 +6847,40 @@ pre.command-output {
|
||||
margin: 0 !important;
|
||||
padding: var(--field-gap) var(--section-inset);
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox,
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox,
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-align: center;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-head:first-child,
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-head:first-child {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-head:first-child,
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-head:first-child {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body,
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body,
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body .cbi-tooltip-container,
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body .cbi-tooltip-container {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body .cbi-tooltip-container,
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body .cbi-tooltip-container {
|
||||
font-size: inherit !important;
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
[dir="rtl"] [data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip,
|
||||
[dir="rtl"] [data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip {
|
||||
[dir="rtl"] [data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip,
|
||||
[dir="rtl"] [data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox .ifacebox-body .cbi-tooltip-container .cbi-tooltip {
|
||||
direction: rtl;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-body:nth-child(2),
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-body:nth-child(2) {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-body:nth-child(2),
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-body:nth-child(2) {
|
||||
flex-grow: 1;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-page="admin-status"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-body:last-child,
|
||||
[data-page="admin-status-overview"] #view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox > .ifacebox-body:last-child {
|
||||
[data-page="admin-status"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-body:last-child,
|
||||
[data-page="admin-status-overview"] #view div:is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid) > .ifacebox > .ifacebox-body:last-child {
|
||||
padding: 0.5rem 0.625rem;
|
||||
}
|
||||
[data-page="admin-status"] .argon-port-traffic,
|
||||
@@ -9157,6 +9257,12 @@ pre.command-output {
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 600px) {
|
||||
@supports (-moz-appearance: none) {
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary) transparent;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="M6 4h4v16H6zM14 4h4v16h-4z"/></svg>
|
||||
|
After Width: | Height: | Size: 180 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="m8 4 12 8-12 8z"/></svg>
|
||||
|
After Width: | Height: | Size: 169 B |
@@ -60,7 +60,10 @@ return baseclass.extend({
|
||||
this.panel = E('fieldset', { id: 'argon-appearance-options', 'class': 'argon-appearance-panel', hidden: true }, [
|
||||
E('legend', { 'class': 'visually-hidden' }, _('Theme mode')),
|
||||
...this.radios.map(function(radio) {
|
||||
return E('label', { 'class': 'argon-appearance-option', 'data-mode': radio.value }, [
|
||||
return E('label', {
|
||||
'class': 'argon-appearance-option', 'data-mode': radio.value,
|
||||
'aria-disabled': !this.writable ? 'true' : null
|
||||
}, [
|
||||
radio, E('span', {}, this.labels[radio.value])
|
||||
]);
|
||||
}.bind(this)),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
|
||||
// Firefox ESR 115 predates :has(). Keep common form controls usable without
|
||||
// changing browser preferences or running a selector polyfill on every page.
|
||||
return baseclass.extend({
|
||||
__init__: function() {
|
||||
if (CSS.supports('selector(:has(*))'))
|
||||
return;
|
||||
var root = document.body;
|
||||
if (!root)
|
||||
return;
|
||||
var sectionSelector = '.cbi-tblsection, .cbi-tblsection .table';
|
||||
var update = function(records) {
|
||||
var groups = new Set(), sections = new Set();
|
||||
var collect = function(node) {
|
||||
if (node.nodeType !== 1)
|
||||
return;
|
||||
if (node.matches('.control-group'))
|
||||
groups.add(node);
|
||||
if (node.matches(sectionSelector))
|
||||
sections.add(node);
|
||||
node.querySelectorAll('.control-group').forEach(function(group) { groups.add(group); });
|
||||
node.querySelectorAll(sectionSelector).forEach(function(section) { sections.add(section); });
|
||||
};
|
||||
if (!records)
|
||||
collect(root);
|
||||
else records.forEach(function(record) {
|
||||
// Counter/text updates cannot change these structural relationships.
|
||||
if (!Array.from(record.addedNodes).concat(Array.from(record.removedNodes))
|
||||
.some(function(node) { return node.nodeType === 1; }))
|
||||
return;
|
||||
for (var parent = record.target; parent && parent !== root; parent = parent.parentElement) {
|
||||
if (parent.matches('.control-group'))
|
||||
groups.add(parent);
|
||||
if (parent.matches(sectionSelector))
|
||||
sections.add(parent);
|
||||
}
|
||||
record.addedNodes.forEach(collect);
|
||||
});
|
||||
groups.forEach(function(group) {
|
||||
var input = group.firstElementChild;
|
||||
group.classList.toggle('argon-joined-control', !!(input && input.matches('input') &&
|
||||
input.nextElementSibling && input.nextElementSibling.matches('.cbi-button')));
|
||||
});
|
||||
sections.forEach(function(section) {
|
||||
section.classList.toggle('argon-dropdown-section', !!section.querySelector('.cbi-dropdown'));
|
||||
});
|
||||
};
|
||||
// Observe native LuCI renders and modal contents, including dependent fields.
|
||||
new MutationObserver(update).observe(root, { childList: true, subtree: true });
|
||||
update();
|
||||
}
|
||||
});
|
||||
@@ -13,6 +13,7 @@ return baseclass.extend({
|
||||
var frame = null;
|
||||
var update = function() {
|
||||
frame = null;
|
||||
var pending = [];
|
||||
view.querySelectorAll('svg').forEach(function(svg) {
|
||||
if (graphs.has(svg) || svg.hasAttribute('viewBox'))
|
||||
return;
|
||||
@@ -28,11 +29,16 @@ return baseclass.extend({
|
||||
if (width <= 0 || !(height > 0))
|
||||
return;
|
||||
|
||||
graphs.set(svg, width);
|
||||
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
||||
svg.setAttribute('preserveAspectRatio', 'none');
|
||||
pending.push({ svg: svg, width: width, height: height });
|
||||
});
|
||||
|
||||
pending.forEach(function(graph) {
|
||||
graphs.set(graph.svg, graph.width);
|
||||
graph.svg.setAttribute('viewBox', '0 0 ' + graph.width + ' ' + graph.height);
|
||||
graph.svg.setAttribute('preserveAspectRatio', 'none');
|
||||
});
|
||||
|
||||
var labels = [];
|
||||
graphs.forEach(function(width, svg) {
|
||||
if (!svg.isConnected) {
|
||||
graphs.delete(svg);
|
||||
@@ -47,18 +53,26 @@ return baseclass.extend({
|
||||
svg.querySelectorAll('text').forEach(function(text) {
|
||||
var x = parseFloat(text.getAttribute('x')) || 0;
|
||||
var offset = x * (1 - inverse);
|
||||
var transform = function(shift) {
|
||||
text.style.transform = 'translateX(' + shift + 'px) scaleX(' + inverse + ')';
|
||||
};
|
||||
text.style.transformOrigin = '0 0';
|
||||
transform(offset);
|
||||
var label = text.getBoundingClientRect();
|
||||
var shift = Math.max(0, bounds.left + 4 - label.left) -
|
||||
Math.max(0, label.right - bounds.right + 4);
|
||||
if (shift)
|
||||
transform(offset + shift / ratio);
|
||||
labels.push({ text: text, bounds: bounds, ratio: ratio, inverse: inverse, offset: offset });
|
||||
});
|
||||
});
|
||||
|
||||
// Apply every provisional transform before measuring any label. Alternating
|
||||
// a style write and a bounds read forces layout once per chart label.
|
||||
labels.forEach(function(label) {
|
||||
label.text.style.transformOrigin = '0 0';
|
||||
label.text.style.transform = 'translateX(' + label.offset + 'px) scaleX(' + label.inverse + ')';
|
||||
});
|
||||
labels.forEach(function(label) {
|
||||
var bounds = label.text.getBoundingClientRect();
|
||||
label.shift = Math.max(0, label.bounds.left + 4 - bounds.left) -
|
||||
Math.max(0, bounds.right - label.bounds.right + 4);
|
||||
});
|
||||
labels.forEach(function(label) {
|
||||
if (label.shift)
|
||||
label.text.style.transform = 'translateX(' + (label.offset + label.shift / label.ratio) +
|
||||
'px) scaleX(' + label.inverse + ')';
|
||||
});
|
||||
};
|
||||
var schedule = function() {
|
||||
if (frame === null)
|
||||
|
||||
@@ -11,7 +11,12 @@ return baseclass.extend({
|
||||
var frame = null;
|
||||
var update = function() {
|
||||
frame = null;
|
||||
view.querySelectorAll('.ifacebox:has(> .ifacebox-body > img[src*="/icons/port_"]) > .ifacebox-body:last-child > .cbi-tooltip-container').forEach(function(container) {
|
||||
view.querySelectorAll('.ifacebox > .ifacebox-body > img[src*="/icons/port_"]').forEach(function(icon) {
|
||||
var box = icon.parentNode.parentNode;
|
||||
box.parentNode.classList.add('argon-port-grid');
|
||||
var container = box.lastElementChild.querySelector('.cbi-tooltip-container');
|
||||
if (!container)
|
||||
return;
|
||||
if (container.querySelector('.argon-port-traffic'))
|
||||
return;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ return view.extend({
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('argon', _('Argon theme configuration'),
|
||||
_('Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended.'));
|
||||
_('Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos.'));
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
s.render = function() { return updates.render(data[4], this.map); };
|
||||
|
||||
@@ -12,6 +12,25 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
z-index: 1200;
|
||||
inset-block-start: var(--control-gap);
|
||||
inset-inline-start: var(--page-gutter);
|
||||
padding: .5rem .75rem;
|
||||
color: var(--text-strong);
|
||||
background: var(--surface-panel);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-control);
|
||||
transform: translateY(calc(-100% - var(--control-gap)));
|
||||
|
||||
&:focus {
|
||||
transform: none;
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.main-left,
|
||||
.main-right,
|
||||
|
||||
@@ -162,21 +162,12 @@ header .header-actions {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
&:has(input:checked) {
|
||||
color: var(--dropdown-selected-text);
|
||||
background: var(--dropdown-selected-bg);
|
||||
|
||||
&::after {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input:focus-visible) {
|
||||
&:focus-within {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&:has(input:disabled) {
|
||||
&[aria-disabled="true"] {
|
||||
cursor: default;
|
||||
opacity: .6;
|
||||
}
|
||||
@@ -201,7 +192,7 @@ header .header-actions {
|
||||
background-color: var(--surface-active);
|
||||
}
|
||||
|
||||
.argon-appearance-option:has(input:not(:disabled)):not(:has(input:checked)):hover {
|
||||
.argon-appearance-option:not([aria-disabled="true"]):hover {
|
||||
background: var(--dropdown-hover-bg);
|
||||
}
|
||||
}
|
||||
@@ -226,6 +217,19 @@ header .header-actions {
|
||||
}
|
||||
}
|
||||
|
||||
// The existing mode attribute tracks the checked radio, including save rollback.
|
||||
// Explicit selectors also preserve selection and its checkmark on Firefox ESR 115.
|
||||
#argon-appearance[data-mode="light"] .argon-appearance-option[data-mode="light"],
|
||||
#argon-appearance[data-mode="dark"] .argon-appearance-option[data-mode="dark"],
|
||||
#argon-appearance[data-mode="normal"] .argon-appearance-option[data-mode="normal"] {
|
||||
color: var(--dropdown-selected-text);
|
||||
background: var(--dropdown-selected-bg);
|
||||
|
||||
&::after {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit selectors keep these rules scoped when compiled with Less 4.
|
||||
// Native hidden semantics still remove the closed menu from focus and hit testing.
|
||||
@starting-style {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
.alert-message {
|
||||
transform: none;
|
||||
position: static;
|
||||
// Inline banners can be flex items, where z-index applies even without positioning.
|
||||
// Keep them below dropdowns instead of inheriting the fixed alert's overlay layer.
|
||||
z-index: auto;
|
||||
transition: opacity .4s ease;
|
||||
|
||||
&.fade-out {
|
||||
|
||||
@@ -212,7 +212,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.control-group:has(> input:first-child + .cbi-button) {
|
||||
// Interpolation preserves the forgiving selector list with the pinned Less 4 parser.
|
||||
@joined-control-selector: ~":is(:has(> input:first-child + .cbi-button), .argon-joined-control)";
|
||||
.control-group@{joined-control-selector} {
|
||||
flex-wrap: nowrap;
|
||||
gap: 0;
|
||||
|
||||
|
||||
@@ -122,6 +122,15 @@
|
||||
|
||||
.cbi-tblsection {
|
||||
overflow-x: auto;
|
||||
|
||||
// A horizontal scroller also computes overflow-y:auto. LuCI then sizes
|
||||
// dropdowns against this short section instead of the page or modal.
|
||||
// Native responsive tables wrap their fields when the viewport narrows.
|
||||
@dropdown-section-selector: ~":is(:has(.cbi-dropdown), .argon-dropdown-section)";
|
||||
&@{dropdown-section-selector},
|
||||
.table@{dropdown-section-selector} {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
|
||||
@@ -517,7 +517,8 @@ footer {
|
||||
box-shadow: var(--section-shadow);
|
||||
}
|
||||
|
||||
&>div:first-child:not(.cbi-map) {
|
||||
// The native dashboard supplies its own grid layout.
|
||||
&>div:first-child:not(.cbi-map, .Dashboard) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
min-width: ~"min(100%, 18rem)";
|
||||
padding-block-end: var(--field-gap);
|
||||
|
||||
// Newer dashboard styles reset chart headings after the theme loads.
|
||||
> h3:first-child {
|
||||
padding: var(--field-gap) var(--section-inset);
|
||||
}
|
||||
|
||||
// Keep the native full-width heading; inset its chart and legend.
|
||||
> :not(h3:first-child) {
|
||||
margin-inline: var(--section-inset);
|
||||
@@ -77,4 +82,58 @@
|
||||
[data-tab="layout"] .cbi-page-actions {
|
||||
padding: var(--field-gap) var(--section-inset);
|
||||
}
|
||||
|
||||
.dashboard-widget-list > .item {
|
||||
align-items: flex-start;
|
||||
// Dashboard entries support dragging and contain native form controls.
|
||||
pointer-events: auto;
|
||||
|
||||
// Fill the row so the delete button stays at LuCI's right-edge hit area.
|
||||
> span {
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-widget-settings {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-control) 0 0 var(--radius-control);
|
||||
|
||||
> summary {
|
||||
box-sizing: border-box;
|
||||
min-height: calc(2.5rem - 2px);
|
||||
padding: .5rem .75rem;
|
||||
line-height: 1.5;
|
||||
list-style-position: inside;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: -2px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// The item remains LTR because its native delete hit area is physical.
|
||||
[dir="rtl"] & {
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-widget-options {
|
||||
margin: 0;
|
||||
padding: var(--control-gap) .75rem;
|
||||
|
||||
.cbi-checkbox {
|
||||
height: auto;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
// Each checkbox is already a flex row; native <br> nodes add empty rows.
|
||||
.cbi-checkbox + br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,8 @@
|
||||
}
|
||||
|
||||
// Match native port cards by their icons, independently of inline CSS formatting.
|
||||
#view div:has(> .ifacebox > .ifacebox-body > img[src*="/icons/port_"]) {
|
||||
@port-grid-selector: ~":is(:has(> .ifacebox > .ifacebox-body > img[src*=\"/icons/port_\"]), .argon-port-grid)";
|
||||
#view div@{port-grid-selector} {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fit, minmax(~"min(100%, 8.5rem)", 1fr)) !important;
|
||||
// ImmortalWrt centers fixed-width cards; let both native variants fill a row.
|
||||
|
||||
@@ -556,6 +556,14 @@
|
||||
// ==========================================================================
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
// Firefox uses the standard scrollbar API rather than WebKit pseudo-elements.
|
||||
@supports (-moz-appearance: none) {
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@@ -42,24 +42,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
.volume-control {
|
||||
.background-controls {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
inset-block-start: var(--field-gap);
|
||||
inset-inline-end: var(--field-gap);
|
||||
display: flex;
|
||||
gap: var(--control-gap);
|
||||
z-index: 5000;
|
||||
cursor: pointer;
|
||||
border: 0 !important;
|
||||
background: url(../img/volume_high.svg) no-repeat center/contain;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 4px;
|
||||
> button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
padding: .5rem;
|
||||
cursor: pointer;
|
||||
background: var(--surface-panel);
|
||||
color: var(--text-strong);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: currentColor;
|
||||
mask: var(--background-control-icon) center / contain no-repeat;
|
||||
-webkit-mask: var(--background-control-icon) center / contain no-repeat;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
&::before {
|
||||
forced-color-adjust: none;
|
||||
background-color: ButtonText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playback-control {
|
||||
--background-control-icon: url(../icon/play.svg);
|
||||
|
||||
&.playing {
|
||||
--background-control-icon: url(../icon/pause.svg);
|
||||
}
|
||||
}
|
||||
|
||||
.volume-control {
|
||||
--background-control-icon: url(../img/volume_high.svg);
|
||||
|
||||
&.mute {
|
||||
background-image: url(../img/volume_off.svg);
|
||||
--background-control-icon: url(../img/volume_off.svg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "السماح للسمة luci-theme-argon بالوصول إلى UCI"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "يمكنك هنا ضبط التمويه والشفافية لصفحة تسجيل الدخول في سمة Argon، وإدارة صور الخلفية ومقاطع الفيديو. يُنصح باستخدام Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "يمكنك هنا ضبط التمويه والشفافية لصفحة تسجيل الدخول في سمة Argon، وإدارة صور الخلفية ومقاطع الفيديو."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "إلغاء"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "حاول مجددًا بعد %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "تشغيل فيديو الخلفية"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "إيقاف فيديو الخلفية مؤقتًا"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "تخطي إلى المحتوى"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "UCI-Zugriff für luci-theme-argon erlauben"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Hier können Sie die Unschärfe und Transparenz der Anmeldeseite des Argon-Designs einstellen sowie Hintergrundbilder und -videos verwalten. Chrome wird empfohlen."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Hier können Sie die Unschärfe und Transparenz der Anmeldeseite des Argon-Designs einstellen sowie Hintergrundbilder und -videos verwalten."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Abbrechen"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Versuchen Sie es nach %s erneut."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Hintergrundvideo abspielen"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Hintergrundvideo pausieren"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Zum Inhalt springen"
|
||||
@@ -165,8 +165,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Permitir el acceso a UCI a luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Aquí puede configurar el desenfoque y la transparencia de la página de inicio de sesión del tema Argon y administrar las imágenes y los vídeos de fondo. Se recomienda Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Aquí puede configurar el desenfoque y la transparencia de la página de inicio de sesión del tema Argon y administrar las imágenes y los vídeos de fondo."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -538,3 +538,15 @@ msgstr "Cancelar"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Vuelva a intentarlo después de las %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Reproducir el vídeo de fondo"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Pausar el vídeo de fondo"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Saltar al contenido"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Autoriser l’accès à UCI pour luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Vous pouvez régler ici le flou et la transparence de la page de connexion du thème Argon, ainsi que gérer les images et les vidéos d’arrière-plan. Chrome est recommandé."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Vous pouvez régler ici le flou et la transparence de la page de connexion du thème Argon, ainsi que gérer les images et les vidéos d’arrière-plan."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Annuler"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Réessayez après %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Lire la vidéo d’arrière-plan"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Mettre la vidéo d’arrière-plan en pause"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Aller au contenu"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Consenti l’accesso UCI a luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Qui puoi impostare la sfocatura e la trasparenza della pagina di accesso del tema Argon e gestire le immagini e i video di sfondo. Si consiglia Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Qui puoi impostare la sfocatura e la trasparenza della pagina di accesso del tema Argon e gestire le immagini e i video di sfondo."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Annulla"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Riprovare dopo le %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Riproduci il video di sfondo"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Metti in pausa il video di sfondo"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Vai al contenuto"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "luci-theme-argon に UCI へのアクセスを許可"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Argon テーマのログインページのぼかしと透明度を設定し、背景画像や動画を管理できます。Chrome の使用を推奨します。"
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Argon テーマのログインページのぼかしと透明度を設定し、背景画像や動画を管理できます。"
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "キャンセル"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "%s 以降にもう一度お試しください。"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "背景動画を再生"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "背景動画を一時停止"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "コンテンツへスキップ"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "luci-theme-argon에 UCI 접근 허용"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Argon 테마 로그인 페이지의 흐림 효과와 투명도를 설정하고 배경 이미지와 동영상을 관리할 수 있습니다. Chrome 사용을 권장합니다."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Argon 테마 로그인 페이지의 흐림 효과와 투명도를 설정하고 배경 이미지와 동영상을 관리할 수 있습니다."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "취소"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "%s 이후에 다시 시도하세요."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "배경 동영상 재생"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "배경 동영상 일시 중지"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "본문으로 건너뛰기"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "UCI-toegang verlenen aan luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Hier kunt u de vervaging en transparantie van de aanmeldpagina van het Argon-thema instellen en achtergrondafbeeldingen en -video’s beheren. Chrome wordt aanbevolen."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Hier kunt u de vervaging en transparantie van de aanmeldpagina van het Argon-thema instellen en achtergrondafbeeldingen en -video’s beheren."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Annuleren"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Probeer het na %s opnieuw."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Achtergrondvideo afspelen"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Achtergrondvideo pauzeren"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Naar inhoud springen"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Przyznaj dostęp do UCI dla luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Tutaj możesz ustawić rozmycie i przezroczystość strony logowania motywu Argon, oraz zarządzać obrazami i filmami tła. Zalecana jest przeglądarka Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Tutaj możesz ustawić rozmycie i przezroczystość strony logowania motywu Argon, oraz zarządzać obrazami i filmami tła."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Anuluj"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Spróbuj ponownie po %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Odtwórz wideo w tle"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Wstrzymaj wideo w tle"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Przejdź do treści"
|
||||
@@ -165,8 +165,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Permitir acesso à UCI para luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Aqui você pode configurar o desfoque e a transparência da página de login do tema Argon e gerenciar as imagens e os vídeos de fundo. Recomenda-se o Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Aqui você pode configurar o desfoque e a transparência da página de login do tema Argon e gerenciar as imagens e os vídeos de fundo."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -538,3 +538,15 @@ msgstr "Cancelar"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Tente novamente após %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Reproduzir vídeo de fundo"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Pausar vídeo de fundo"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Ir para o conteúdo"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Разрешить luci-theme-argon доступ к UCI"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Здесь можно настроить размытие и прозрачность страницы логина темы Argon, управлять фоновыми изображениями и видео. Рекомендуется использовать Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Здесь можно настроить размытие и прозрачность страницы логина темы Argon, управлять фоновыми изображениями и видео."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Отмена"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Повторите попытку после %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Воспроизвести фоновое видео"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Приостановить фоновое видео"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Перейти к содержимому"
|
||||
@@ -160,7 +160,7 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid ""
|
||||
"Here you can set the blur and transparency of the login page of argon theme, "
|
||||
"and manage the background pictures and videos. Chrome is recommended."
|
||||
"and manage the background pictures and videos."
|
||||
msgstr ""
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
@@ -543,3 +543,15 @@ msgstr ""
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr ""
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr ""
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr ""
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr ""
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "luci-theme-argon için UCI erişimine izin ver"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Burada Argon temasının giriş sayfasının bulanıklığını ve saydamlığını ayarlayabilir, arka plan resimlerini ve videolarını yönetebilirsiniz. Chrome kullanılması önerilir."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Burada Argon temasının giriş sayfasının bulanıklığını ve saydamlığını ayarlayabilir, arka plan resimlerini ve videolarını yönetebilirsiniz."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "İptal"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "%s sonrasında yeniden deneyin."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Arka plan videosunu oynat"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Arka plan videosunu duraklat"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "İçeriğe atla"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Надати luci-theme-argon доступ до UCI"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Тут можна налаштувати розмиття та прозорість сторінки входу теми Argon, а також керувати фоновими зображеннями та відео. Рекомендовано використовувати Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Тут можна налаштувати розмиття та прозорість сторінки входу теми Argon, а також керувати фоновими зображеннями та відео."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Скасувати"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Спробуйте ще раз після %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Відтворити фонове відео"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Призупинити фонове відео"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Перейти до вмісту"
|
||||
@@ -163,8 +163,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "Cấp quyền truy cập UCI cho luci-theme-argon"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "Tại đây, bạn có thể thiết lập độ mờ và độ trong suốt của trang đăng nhập giao diện Argon, đồng thời quản lý hình ảnh và video nền. Khuyến nghị sử dụng Chrome."
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "Tại đây, bạn có thể thiết lập độ mờ và độ trong suốt của trang đăng nhập giao diện Argon, đồng thời quản lý hình ảnh và video nền."
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -536,3 +536,15 @@ msgstr "Hủy"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "Thử lại sau %s."
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "Phát video nền"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "Tạm dừng video nền"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "Chuyển đến nội dung"
|
||||
@@ -165,8 +165,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "授予 luci-theme-argon 访问 UCI 配置的权限"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "设置 Argon 主题登录页的模糊程度和透明度,管理背景图片与视频。建议使用 Chrome 浏览器。"
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "设置 Argon 主题登录页的模糊程度和透明度,管理背景图片与视频。"
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -538,3 +538,15 @@ msgstr "取消"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "请在 %s 后重试。"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "播放背景视频"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "暂停背景视频"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "跳至主要内容"
|
||||
@@ -165,8 +165,8 @@ msgid "Grant UCI access for luci-theme-argon"
|
||||
msgstr "授予 luci-theme-argon 存取 UCI 設定的權限"
|
||||
|
||||
#: htdocs/luci-static/resources/view/argon-config.js:114
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended."
|
||||
msgstr "您可以在此設定登入畫面的模糊度、透明度、以及管理背景圖片與影片(推薦使用 Chrome)"
|
||||
msgid "Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos."
|
||||
msgstr "您可以在此設定登入畫面的模糊度、透明度、以及管理背景圖片與影片。"
|
||||
|
||||
#: htdocs/luci-static/resources/argon/branding.js:60
|
||||
msgid "Invalid PNG image or dimensions."
|
||||
@@ -538,3 +538,15 @@ msgstr "取消"
|
||||
#: htdocs/luci-static/resources/argon/updates.js
|
||||
msgid "Try again after %s."
|
||||
msgstr "請在 %s 後重試。"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Play background video"
|
||||
msgstr "播放背景影片"
|
||||
|
||||
#: ucode/template/themes/argon/login_background.ut
|
||||
msgid "Pause background video"
|
||||
msgstr "暫停背景影片"
|
||||
|
||||
#: ucode/template/themes/argon/header.ut
|
||||
msgid "Skip to content"
|
||||
msgstr "跳至主要內容"
|
||||
@@ -75,6 +75,7 @@ test('failed writes reconcile stored state and restore usable controls', async (
|
||||
test('read-only, unavailable, invalid, and repeated mode changes never write', async () => {
|
||||
const readonly = fixture({ writable: false }); await readonly.start(); await readonly.api.saveMode('dark');
|
||||
assert.ok(readonly.api.radios.every(r => r.disabled)); assert.equal(readonly.calls.filter(c => c[0] === 'set').length, 0);
|
||||
assert.ok(readonly.api.panel.children.filter(node => node.tag === 'label').every(node => node.attrs['aria-disabled'] === 'true'));
|
||||
const offline = fixture({ getError: true }); await offline.start(); assert.equal(offline.root.hidden, true);
|
||||
const state = fixture(); await state.start(); await state.api.saveMode('bogus'); await state.api.saveMode('normal');
|
||||
assert.equal(state.calls.filter(c => c[0] === 'set').length, 0);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const template = fs.readFileSync(path.join(__dirname, '../ucode/template/themes/argon/login_background.ut'), 'utf8');
|
||||
const script = template.match(/<script>([\s\S]*?)<\/script>/)[1];
|
||||
function fixture(reduced = false, blocked = false) {
|
||||
const video = Object.assign(new EventTarget(), { paused: true, muted: true,
|
||||
play() { if (blocked) return Promise.reject(Error('blocked'));this.paused = false;this.dispatchEvent(new Event('play'));return Promise.resolve(); },
|
||||
pause() { this.paused = true;this.dispatchEvent(new Event('pause')); }
|
||||
});
|
||||
const classes = new Set();
|
||||
const button = Object.assign(new EventTarget(), { dataset: { playLabel: 'Play', pauseLabel: 'Pause' },attrs: {},
|
||||
classList: { toggle(name, on) { on ? classes.add(name) : classes.delete(name); } },setAttribute(name,value) { this.attrs[name] = value; }
|
||||
});
|
||||
const muteClasses = new Set(['mute']);
|
||||
const mute = Object.assign(new EventTarget(), { attrs: {},classList: { contains: n=>muteClasses.has(n),add: n=>muteClasses.add(n),remove: n=>muteClasses.delete(n) },setAttribute(n,v){this.attrs[n]=v;} });
|
||||
const preference = Object.assign(new EventTarget(), { matches: reduced });
|
||||
const document = Object.assign(new EventTarget(), { hidden: false,getElementById: ()=>video,querySelector: selector=>selector==='.playback-control'?button:mute });
|
||||
new Function('document','window',script)(document,{matchMedia:()=>preference});
|
||||
return {video,button,mute,preference,document,classes,unblock(){blocked=false;}};
|
||||
}
|
||||
|
||||
test('manual pause survives visibility and system motion changes', () => {
|
||||
const s=fixture();assert.equal(s.video.paused,false);assert.equal(s.button.attrs['aria-label'],'Pause');
|
||||
s.button.dispatchEvent(new Event('click'));assert.equal(s.video.paused,true);assert.equal(s.button.attrs['aria-label'],'Play');
|
||||
s.document.hidden=true;s.document.dispatchEvent(new Event('visibilitychange'));s.document.hidden=false;s.document.dispatchEvent(new Event('visibilitychange'));
|
||||
s.preference.matches=true;s.preference.dispatchEvent(new Event('change'));s.preference.matches=false;s.preference.dispatchEvent(new Event('change'));
|
||||
assert.equal(s.video.paused,true);
|
||||
});
|
||||
|
||||
test('reduced motion starts static but permits explicit playback and pauses on a new preference', () => {
|
||||
const s=fixture(true);assert.equal(s.video.paused,true);s.button.dispatchEvent(new Event('click'));assert.equal(s.video.paused,false);
|
||||
s.document.hidden=true;s.document.dispatchEvent(new Event('visibilitychange'));assert.equal(s.video.paused,true);
|
||||
s.document.hidden=false;s.document.dispatchEvent(new Event('visibilitychange'));assert.equal(s.video.paused,false);
|
||||
s.preference.matches=false;s.preference.dispatchEvent(new Event('change'));s.preference.matches=true;s.preference.dispatchEvent(new Event('change'));assert.equal(s.video.paused,true);
|
||||
});
|
||||
|
||||
test('blocked autoplay stays playable and muting does not change playback preference', async () => {
|
||||
const s=fixture(false,true);await Promise.resolve();assert.equal(s.button.attrs['aria-label'],'Play');assert.equal(s.classes.has('playing'),false);
|
||||
s.unblock();s.button.dispatchEvent(new Event('click'));assert.equal(s.video.paused,false);
|
||||
s.mute.dispatchEvent(new Event('click'));assert.equal(s.video.muted,false);assert.equal(s.mute.attrs['aria-pressed'],'false');assert.equal(s.video.paused,false);
|
||||
s.mute.dispatchEvent(new Event('click'));assert.equal(s.video.muted,true);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const source = fs.readFileSync(path.join(__dirname, '../htdocs/luci-static/resources/argon-compat.js'), 'utf8');
|
||||
|
||||
function fixture(nativeHas) {
|
||||
const groups = [], sections = [], observers = [];
|
||||
let scans = 0;
|
||||
const root = { nodeType: 1, matches: () => false, querySelectorAll: selector => { scans++; return selector === '.control-group' ? groups : sections; } };
|
||||
const api = new Function('baseclass', 'CSS', 'document', 'MutationObserver', source)(
|
||||
{ extend: value => value }, { supports: () => nativeHas }, { body: root },
|
||||
function(callback) { this.observe = (node, options) => observers.push({ node, options, callback }); }
|
||||
);
|
||||
return { groups, sections, observers, root, scans: () => scans, start: () => api.__init__(), update: records => observers[0].callback(records) };
|
||||
}
|
||||
function node(kind) {
|
||||
const classes = new Set();
|
||||
return { nodeType: 1, matches: selector => kind === 'group' ? selector === '.control-group' : selector !== '.control-group', querySelectorAll: () => [], classes, classList: { toggle(name, enabled) { enabled ? classes.add(name) : classes.delete(name); } } };
|
||||
}
|
||||
|
||||
test('browsers with native relational selectors install no compatibility observer', () => {
|
||||
const state = fixture(true);
|
||||
state.start();
|
||||
assert.equal(state.observers.length, 0);
|
||||
});
|
||||
|
||||
test('legacy controls and dropdown sections update after native renders and removals', () => {
|
||||
const state = fixture(false);
|
||||
state.start();
|
||||
assert.equal(state.observers.length, 1);
|
||||
assert.deepEqual(state.observers[0].options, { childList: true, subtree: true });
|
||||
const joined = node('group'), plain = node('group'), table = node('section');
|
||||
joined.firstElementChild = { matches: selector => selector === 'input', nextElementSibling: { matches: selector => selector === '.cbi-button' } };
|
||||
plain.firstElementChild = { matches: () => false };
|
||||
let dropdown = {};
|
||||
table.querySelector = () => dropdown;
|
||||
state.groups.push(joined, plain); state.sections.push(table); state.update([{ target: state.root, addedNodes: [joined, plain, table], removedNodes: [] }]);
|
||||
assert.equal(joined.classes.has('argon-joined-control'), true);
|
||||
assert.equal(plain.classes.size, 0);
|
||||
assert.equal(table.classes.has('argon-dropdown-section'), true);
|
||||
joined.firstElementChild.nextElementSibling = null; dropdown = null; state.update([
|
||||
{ target: joined, addedNodes: [], removedNodes: [{ nodeType: 1 }] },
|
||||
{ target: table, addedNodes: [], removedNodes: [{ nodeType: 1 }] }
|
||||
]);
|
||||
assert.equal(joined.classes.size, 0);
|
||||
assert.equal(table.classes.size, 0);
|
||||
});
|
||||
|
||||
test('counter text updates do not scan the document or unrelated controls', () => {
|
||||
const state = fixture(false); state.start(); const scans = state.scans();
|
||||
state.update([{ target: state.root, addedNodes: [{ nodeType: 3 }], removedNodes: [{ nodeType: 3 }] }]);
|
||||
assert.equal(state.scans(), scans);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const source = fs.readFileSync(process.env.ARGON_GRAPH_SOURCE || path.join(__dirname, '../htdocs/luci-static/resources/argon-graphs.js'), 'utf8');
|
||||
|
||||
function fixture() {
|
||||
const state = { dirty: false, flushes: 0, graphs: [], frames: [] };
|
||||
const read = () => { if (state.dirty) state.flushes++; state.dirty = false; };
|
||||
function graph(left) {
|
||||
const svg = { isConnected: true, attrs: {}, width: 200,
|
||||
hasAttribute: name => name in svg.attrs,
|
||||
setAttribute(name, value) { svg.attrs[name] = value; state.dirty = true; },
|
||||
get clientHeight() { read(); return 100; },
|
||||
getBoundingClientRect() { read(); return { left, right: left + svg.width, width: svg.width }; }
|
||||
};
|
||||
svg.texts = Array.from({ length: 20 }, (_, i) => {
|
||||
const x = i * 500 / 19;
|
||||
const text = { getAttribute: () => x, style: new Proxy({}, { set(obj, key, value) { state.dirty = true; obj[key] = value; return true; } }),
|
||||
getBoundingClientRect() { read(); const values = [...(text.style.transform || '').matchAll(/\(([-\d.]+)/g)].map(m => +m[1]);
|
||||
const ratio = svg.width / 500, start = left + (x * (values[1] || 1) + (values[0] || 0)) * ratio;
|
||||
return { left: start, right: start + 24 };
|
||||
}
|
||||
};return text;
|
||||
});
|
||||
svg.querySelectorAll = selector => selector === 'text' ? svg.texts : [{ points: { numberOfItems: 2, getItem: i => ({ x: i * 500 }) } }];
|
||||
return svg;
|
||||
}
|
||||
state.graphs = [graph(0), graph(250)];
|
||||
const view = { querySelectorAll: () => state.graphs };
|
||||
const api = new Function('baseclass','document','window','ResizeObserver','MutationObserver','requestAnimationFrame',source)(
|
||||
{ extend: x => x }, { querySelector: () => view }, { ResizeObserver: true },
|
||||
function(callback) { state.resize = callback; this.observe = () => {}; }, function() { this.observe = () => {}; }, fn => state.frames.push(fn)
|
||||
);
|
||||
api.__init__();state.run = () => { while (state.frames.length) state.frames.shift()(); };return state;
|
||||
}
|
||||
|
||||
test('chart labels batch layout reads and remain inside resized plots', () => {
|
||||
const state = fixture();state.run();
|
||||
assert.ok(state.flushes <= 2, `Expected at most two layout flushes, got ${state.flushes}`);
|
||||
for (const svg of state.graphs) {
|
||||
assert.equal(svg.attrs.viewBox,'0 0 500 100');
|
||||
for (const text of svg.texts) { const label = text.getBoundingClientRect(),plot = svg.getBoundingClientRect();assert.ok(label.left >= plot.left + 3.9 && label.right <= plot.right - 3.9); }
|
||||
}
|
||||
state.graphs[0].width = 150;state.resize();state.run();
|
||||
for (const text of state.graphs[0].texts) assert.ok(text.getBoundingClientRect().right <= 146.1);
|
||||
});
|
||||
|
||||
test('detached plots stop receiving updates', () => {
|
||||
const state = fixture();state.run();const removed = state.graphs[0];removed.isConnected = false;state.graphs = state.graphs.slice(1);
|
||||
const previous = removed.texts[0].style.transform;removed.width = 50;state.resize();state.run();assert.equal(removed.texts[0].style.transform,previous);
|
||||
});
|
||||
@@ -32,7 +32,8 @@
|
||||
<ul class="breadcrumb pull-right" id="modemenu" style="display:none"></ul>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{% include('themes/' + theme + '/page_scripts', { is_login: false }) %}
|
||||
</body>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<a href="{{ version.disturl }}" target="_blank" title="{{ entityencode(firmware_info, true) }}">{{ firmware_info }}</a>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{% include('themes/' + theme + '/page_scripts', { is_login: true }) %}
|
||||
</body>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
class="lang_{{ dispatcher.lang }} {{ node?.title ? ` - ${striptags(node.title)}` : '' }} {% if (ctx.authsession): %}logged-in{% endif %}"
|
||||
data-page="{{ entityencode(join('-', page_path), true) }}">
|
||||
|
||||
<a class="skip-link" href="#maincontent">{{ _('Skip to content') }}</a>
|
||||
<div class="main">
|
||||
<div class="main-left" id="mainmenu" style="display:none">
|
||||
<div class="sidenav-header d-flex align-items-center">
|
||||
@@ -62,7 +63,7 @@
|
||||
</div>
|
||||
</header>
|
||||
<div class="darkMask"></div>
|
||||
<div id="maincontent">
|
||||
<main id="maincontent" tabindex="-1">
|
||||
<div class="container">
|
||||
{% if (getuid() == 0 && getspnam('root')?.pwdp === ''): %}
|
||||
<div class="alert-message error">
|
||||
|
||||
@@ -70,25 +70,50 @@
|
||||
<source src="{{ entityencode(bgArry.bgUrl, true) }}" type="{{ bgArry.mimeType }}">
|
||||
</video>
|
||||
</div>
|
||||
<button type="button" class="volume-control mute" aria-label="{{ entityencode(_('Mute background video'), true) }}" aria-pressed="true"></button>
|
||||
<div class="background-controls">
|
||||
<button type="button" class="playback-control" aria-controls="video" aria-label="{{ entityencode(_('Play background video'), true) }}" data-play-label="{{ entityencode(_('Play background video'), true) }}" data-pause-label="{{ entityencode(_('Pause background video'), true) }}"></button>
|
||||
<button type="button" class="volume-control mute" aria-controls="video" aria-label="{{ entityencode(_('Mute background video'), true) }}" aria-pressed="true"></button>
|
||||
</div>
|
||||
<script>
|
||||
var backgroundVideo = document.getElementById("video");
|
||||
var playbackControl = document.querySelector('.playback-control');
|
||||
var motionPreference = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
var playbackChoice = null;
|
||||
function syncBackgroundPlayback() {
|
||||
playbackControl.classList.toggle('playing', !backgroundVideo.paused);
|
||||
playbackControl.setAttribute('aria-label', backgroundVideo.paused
|
||||
? playbackControl.dataset.playLabel : playbackControl.dataset.pauseLabel);
|
||||
}
|
||||
function updateBackgroundMotion() {
|
||||
if (motionPreference && motionPreference.matches)
|
||||
var shouldPlay = playbackChoice === true || (playbackChoice !== false &&
|
||||
!(motionPreference && motionPreference.matches));
|
||||
if (!shouldPlay || document.hidden)
|
||||
backgroundVideo.pause();
|
||||
else {
|
||||
var playResult = backgroundVideo.play();
|
||||
if (playResult && playResult.catch)
|
||||
playResult.catch(function() {});
|
||||
playResult.catch(syncBackgroundPlayback);
|
||||
}
|
||||
syncBackgroundPlayback();
|
||||
}
|
||||
backgroundVideo.addEventListener('play', syncBackgroundPlayback);
|
||||
backgroundVideo.addEventListener('pause', syncBackgroundPlayback);
|
||||
playbackControl.addEventListener('click', function() {
|
||||
playbackChoice = backgroundVideo.paused;
|
||||
updateBackgroundMotion();
|
||||
});
|
||||
document.addEventListener('visibilitychange', updateBackgroundMotion);
|
||||
updateBackgroundMotion();
|
||||
if (motionPreference) {
|
||||
var onMotionChange = function() {
|
||||
if (motionPreference.matches && playbackChoice === true)
|
||||
playbackChoice = null;
|
||||
updateBackgroundMotion();
|
||||
};
|
||||
if (motionPreference.addEventListener)
|
||||
motionPreference.addEventListener('change', updateBackgroundMotion);
|
||||
motionPreference.addEventListener('change', onMotionChange);
|
||||
else if (motionPreference.addListener)
|
||||
motionPreference.addListener(updateBackgroundMotion);
|
||||
motionPreference.addListener(onMotionChange);
|
||||
}
|
||||
document.querySelector(".volume-control").addEventListener("click", function(){
|
||||
if(this.classList.contains("mute")){
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
{% if (!is_login): %}
|
||||
<script type="text/javascript">L.require('menu-argon')</script>
|
||||
<script type="text/javascript">
|
||||
L.require('argon-compat');
|
||||
L.require('argon-appearance');
|
||||
if ([ 'admin-status', 'admin-status-overview' ].includes(document.body.dataset.page))
|
||||
L.require('argon-overview');
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="login-page" data-login-style="{{ login_style }}">
|
||||
{% include('themes/' + theme + '/login_background', { cfg: cfg }) %}
|
||||
<!-- Login Container Start -->
|
||||
<div class="login-container">
|
||||
<main class="login-container">
|
||||
<div class="login-form">
|
||||
<!-- Logo Start -->
|
||||
<a class="brand" href="/"><img src="{{ logo_url }}" class="icon" alt="" data-fallback="{{ media }}/img/argon.svg" onerror="this.onerror=null;this.src=this.dataset.fallback">
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
|
||||
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
|
||||
|
||||
PKG_NAME:=mise
|
||||
PKG_VERSION:=2026.11.7
|
||||
PKG_RELEASE:=56
|
||||
PKG_VERSION:=2026.11.8
|
||||
PKG_RELEASE:=57
|
||||
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=v2raya
|
||||
PKG_VERSION:=2.5.15
|
||||
PKG_RELEASE:=9
|
||||
PKG_RELEASE:=10
|
||||
|
||||
PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 Tianling Shen <cnsztl@immortalwrt.org>
|
||||
# Copyright (C) 2026 Amr Fateem <amr.fateem@yahoo.com>
|
||||
#
|
||||
# procd init script for v2rayA.
|
||||
@@ -109,6 +110,7 @@ stop_service() {
|
||||
# process or a proxy-only stop can leave them behind. Remove any
|
||||
# residual state so stopping the service always restores connectivity.
|
||||
nft delete table inet v2raya 2>/dev/null
|
||||
nft delete table inet v2raya_dns 2>/dev/null
|
||||
ip rule del fwmark 0x40/0xc0 table 100 2>/dev/null
|
||||
ip -6 rule del fwmark 0x40/0xc0 table 100 2>/dev/null
|
||||
ip route flush table 100 2>/dev/null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
From 8a3f10e0295a1587fd4beef414386290aca89605 Mon Sep 17 00:00:00 2001
|
||||
From: Amr Fateem <amr.fateem@yahoo.com>
|
||||
Date: Wed, 11 Nov 2026 21:06:01 +0200
|
||||
Subject: [PATCH] openwrt: clean residual transparent-proxy rules on stop
|
||||
Subject: [PATCH] openwrt: clean residual transparent-proxy and DNS rules on stop
|
||||
|
||||
When a core process stops, the in-memory transparentOn flag may be
|
||||
false even though tproxy rules from an earlier process are still
|
||||
@@ -13,10 +13,18 @@ example after a process restart. Clean the residual rules in that path
|
||||
too. The normal path still runs the transparent-proxy hooks and removes
|
||||
the rules as before.
|
||||
|
||||
The nft DNS redirect table (inet v2raya_dns) is installed separately and
|
||||
has no stop hook. Remove it in the same cleanup, otherwise the router's
|
||||
own port-53 traffic keeps going to a listener that has stopped when DNS
|
||||
hijack is enabled.
|
||||
|
||||
Upstream-Status: Pending
|
||||
|
||||
Signed-off-by: Amr Fateem <amr.fateem@yahoo.com>
|
||||
---
|
||||
kernel/v2ray/processManager.go | 15 ++++++++++++---
|
||||
1 file changed, 12 insertions(+), 3 deletions(-)
|
||||
kernel/v2ray/transparent.go | 2 ++
|
||||
2 files changed, 14 insertions(+), 3 deletions(-)
|
||||
|
||||
--- a/kernel/v2ray/processManager.go
|
||||
+++ b/kernel/v2ray/processManager.go
|
||||
@@ -49,3 +57,14 @@ Signed-off-by: Amr Fateem <amr.fateem@yahoo.com>
|
||||
return
|
||||
}
|
||||
if saveRunning {
|
||||
--- a/kernel/v2ray/transparent.go
|
||||
+++ b/kernel/v2ray/transparent.go
|
||||
@@ -210,6 +210,8 @@ func dnsRedirectDeleteCommands() string
|
||||
// to :52353, and once the DNS module exits they hit a dead port and DNS
|
||||
// fails. Idempotent (2>/dev/null || true).
|
||||
func cleanDnsRedirectRules() {
|
||||
+ // NftDNSRedirect installs its table separately and has no stop hook.
|
||||
+ iptables.Setter{Cmds: "nft delete table inet v2raya_dns 2>/dev/null || true"}.Run(false)
|
||||
commands := dnsRedirectDeleteCommands() + `
|
||||
iptables -w 2 -t nat -D OUTPUT -m mark --mark 0x80/0x80 -j RETURN 2>/dev/null || true
|
||||
iptables -w 2 -t nat -D PREROUTING -m mark --mark 0x80/0x80 -j RETURN 2>/dev/null || true
|
||||
Reference in new issue
Block a user