mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
Update On Sun Nov 30 19:38:24 CET 2025
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -1197,3 +1197,4 @@ Update On Wed Nov 26 19:35:47 CET 2025
|
||||
Update On Thu Nov 27 19:38:17 CET 2025
|
||||
Update On Fri Nov 28 19:37:50 CET 2025
|
||||
Update On Sat Nov 29 19:37:01 CET 2025
|
||||
Update On Sun Nov 30 19:38:16 CET 2025
|
||||
|
||||
@@ -32,11 +32,11 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.9
|
||||
github.com/metacubex/sing-tun v0.4.10
|
||||
github.com/metacubex/sing-vmess v0.2.4
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
|
||||
github.com/metacubex/utls v1.8.3
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||
|
||||
@@ -131,16 +131,16 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
|
||||
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E=
|
||||
github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
|
||||
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s=
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
|
||||
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.16",
|
||||
"mihomo_alpha": "alpha-6cf1743",
|
||||
"mihomo_alpha": "alpha-93de49d",
|
||||
"clash_rs": "v0.9.2",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.9.2-alpha+sha.87c7b2c"
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2025-11-28T22:21:05.844Z"
|
||||
"updated_at": "2025-11-29T22:21:10.978Z"
|
||||
}
|
||||
|
||||
20
filebrowser/.github/workflows/docs.yml
vendored
20
filebrowser/.github/workflows/docs.yml
vendored
@@ -28,9 +28,11 @@ jobs:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
name: Build and Release Docs
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
@@ -42,11 +44,9 @@ jobs:
|
||||
uses: go-task/setup-task@v1
|
||||
- name: Build site
|
||||
run: task docs
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
- name: Upload static files as artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
path: www/public
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||
|
||||
## [2.50.0](https://github.com/filebrowser/filebrowser/compare/v2.49.0...v2.50.0) (2025-11-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* configurable logout page URL for proxy/hook auth ([#3884](https://github.com/filebrowser/filebrowser/issues/3884)) ([b9ac45d](https://github.com/filebrowser/filebrowser/commit/b9ac45d5dac4b4eb2ba364629090fbf306cffd2b))
|
||||
* render CSVs as table ([#5569](https://github.com/filebrowser/filebrowser/issues/5569)) ([982405e](https://github.com/filebrowser/filebrowser/commit/982405ec944f94baf43594b0ed2f06329ff4e9ed))
|
||||
* update frontend/src/i18n/hr.json ([279a5cc](https://github.com/filebrowser/filebrowser/commit/279a5ccd1e8d7bde4568b63cb3c506af48b6c618))
|
||||
* update translations ([78e0395](https://github.com/filebrowser/filebrowser/commit/78e039596070a3a9e643a693cc99960c69dcfe92))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not close editor if save failed ([701522a](https://github.com/filebrowser/filebrowser/commit/701522a0600cfa542469540ed764630c0ba1a732)), closes [#5591](https://github.com/filebrowser/filebrowser/issues/5591)
|
||||
|
||||
## [2.49.0](https://github.com/filebrowser/filebrowser/compare/v2.48.2...v2.49.0) (2025-11-22)
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||
flags.String("auth.command", "", "command for auth.method=hook")
|
||||
flags.String("auth.logoutPage", "", "url of custom logout page")
|
||||
|
||||
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
||||
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
||||
@@ -201,6 +202,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||
fmt.Fprintf(w, "Logout Page:\t%s\n", set.LogoutPage)
|
||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||
@@ -328,6 +330,8 @@ func getSettings(flags *pflag.FlagSet, set *settings.Settings, ser *settings.Ser
|
||||
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
||||
case "auth.method":
|
||||
hasAuth = true
|
||||
case "auth.logoutPage":
|
||||
set.LogoutPage, err = flags.GetString(flag.Name)
|
||||
case "branding.name":
|
||||
set.Branding.Name, err = flags.GetString(flag.Name)
|
||||
case "branding.theme":
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
DisableUsedPercentage: false,
|
||||
EnableExec: true,
|
||||
EnableThumbs: true,
|
||||
LogoutPage: "",
|
||||
LoginPage: true,
|
||||
Name: "",
|
||||
NoAuth: false,
|
||||
|
||||
22
filebrowser/frontend/pnpm-lock.yaml
generated
22
filebrowser/frontend/pnpm-lock.yaml
generated
@@ -119,7 +119,7 @@ importers:
|
||||
version: 6.0.2(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3))
|
||||
'@vue/eslint-config-prettier':
|
||||
specifier: ^10.2.0
|
||||
version: 10.2.0(eslint@9.39.1)(prettier@3.7.2)
|
||||
version: 10.2.0(eslint@9.39.1)(prettier@3.7.3)
|
||||
'@vue/eslint-config-typescript':
|
||||
specifier: ^14.6.0
|
||||
version: 14.6.0(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.2.0(eslint@9.39.1)))(eslint@9.39.1)(typescript@5.9.3)
|
||||
@@ -137,7 +137,7 @@ importers:
|
||||
version: 10.1.8(eslint@9.39.1)
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.5.1
|
||||
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2)
|
||||
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.3)
|
||||
eslint-plugin-vue:
|
||||
specifier: ^10.5.1
|
||||
version: 10.6.2(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.2.0(eslint@9.39.1))
|
||||
@@ -146,7 +146,7 @@ importers:
|
||||
version: 8.5.6
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.7.2
|
||||
version: 3.7.3
|
||||
terser:
|
||||
specifier: ^5.43.1
|
||||
version: 5.44.1
|
||||
@@ -2167,8 +2167,8 @@ packages:
|
||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
prettier@3.7.2:
|
||||
resolution: {integrity: sha512-n3HV2J6QhItCXndGa3oMWvWFAgN1ibnS7R9mt6iokScBOC0Ul9/iZORmU2IWUMcyAQaMPjTlY3uT34TqocUxMA==}
|
||||
prettier@3.7.3:
|
||||
resolution: {integrity: sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
@@ -3882,12 +3882,12 @@ snapshots:
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/eslint-config-prettier@10.2.0(eslint@9.39.1)(prettier@3.7.2)':
|
||||
'@vue/eslint-config-prettier@10.2.0(eslint@9.39.1)(prettier@3.7.3)':
|
||||
dependencies:
|
||||
eslint: 9.39.1
|
||||
eslint-config-prettier: 10.1.8(eslint@9.39.1)
|
||||
eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2)
|
||||
prettier: 3.7.2
|
||||
eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.3)
|
||||
prettier: 3.7.3
|
||||
transitivePeerDependencies:
|
||||
- '@types/eslint'
|
||||
|
||||
@@ -4219,10 +4219,10 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.39.1
|
||||
|
||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2):
|
||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.3):
|
||||
dependencies:
|
||||
eslint: 9.39.1
|
||||
prettier: 3.7.2
|
||||
prettier: 3.7.3
|
||||
prettier-linter-helpers: 1.0.0
|
||||
synckit: 0.11.11
|
||||
optionalDependencies:
|
||||
@@ -4697,7 +4697,7 @@ snapshots:
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
|
||||
prettier@3.7.2: {}
|
||||
prettier@3.7.3: {}
|
||||
|
||||
pretty-bytes@7.1.0: {}
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ import {
|
||||
disableExternal,
|
||||
disableUsedPercentage,
|
||||
noAuth,
|
||||
logoutPage,
|
||||
loginPage,
|
||||
} from "@/utils/constants";
|
||||
import { files as api } from "@/api";
|
||||
@@ -159,7 +160,7 @@ export default {
|
||||
version: () => version,
|
||||
disableExternal: () => disableExternal,
|
||||
disableUsedPercentage: () => disableUsedPercentage,
|
||||
canLogout: () => !noAuth && loginPage,
|
||||
canLogout: () => !noAuth && (loginPage || logoutPage !== "/login"),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
<i class="material-icons">description</i>
|
||||
<p>{{ $t("files.lonely") }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="csv-table-container"
|
||||
@wheel.stop
|
||||
@touchmove.stop
|
||||
>
|
||||
<div v-else class="csv-table-container" @wheel.stop @touchmove.stop>
|
||||
<table class="csv-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -66,7 +61,11 @@ const displayError = computed(() => {
|
||||
return props.error;
|
||||
}
|
||||
// Check for parse errors
|
||||
if (props.content && props.content.trim().length > 0 && data.value.headers.length === 0) {
|
||||
if (
|
||||
props.content &&
|
||||
props.content.trim().length > 0 &&
|
||||
data.value.headers.length === 0
|
||||
) {
|
||||
return "Failed to parse CSV file";
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "رفع",
|
||||
"openFile": "فتح الملف",
|
||||
"discardChanges": "إلغاء التغييرات",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "تحميل الملف",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "الترتيب بآخر تعديل",
|
||||
"sortByName": "الترتيب باﻹسم",
|
||||
"sortBySize": "الترتيب بالحجم",
|
||||
"noPreview": "لا يوجد عرض مسبق لهذا الملف."
|
||||
"noPreview": "لا يوجد عرض مسبق لهذا الملف.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "حدد الملف أو المجلد",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Качи",
|
||||
"openFile": "Отвори файл",
|
||||
"discardChanges": "Изчисти",
|
||||
"saveChanges": "Запиши промените"
|
||||
"saveChanges": "Запиши промените",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Свали файл",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Подредба по последна промяна",
|
||||
"sortByName": "Подредба по име",
|
||||
"sortBySize": "Подредба по размер",
|
||||
"noPreview": "За този файл не е наличен преглед."
|
||||
"noPreview": "За този файл не е наличен преглед.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "избери файл или директория",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Pujar",
|
||||
"openFile": "Obrir fitxer",
|
||||
"discardChanges": "Descartar",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarregar fitxer",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordenar per última modificació",
|
||||
"sortByName": "Ordenar per nom",
|
||||
"sortBySize": "Ordenar per mida",
|
||||
"noPreview": "La vista prèvia no està disponible per a aquest fitxer."
|
||||
"noPreview": "La vista prèvia no està disponible per a aquest fitxer.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "seleccionar fitxer o carpeta",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Nahrát",
|
||||
"openFile": "Otevřít soubor",
|
||||
"discardChanges": "Zrušit změny",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stáhnout soubor",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Seřadit podle poslední změny",
|
||||
"sortByName": "Seřadit podle názvu",
|
||||
"sortBySize": "Seřadit podle velikosti",
|
||||
"noPreview": "Náhled pro tento soubor není k dispozici."
|
||||
"noPreview": "Náhled pro tento soubor není k dispozici.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "vyberte soubor nebo adresář",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Upload",
|
||||
"openFile": "Datei öffnen",
|
||||
"discardChanges": "Verwerfen",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download Datei",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Nach Änderungsdatum sortieren",
|
||||
"sortByName": "Nach Namen sortieren",
|
||||
"sortBySize": "Nach Größe sortieren",
|
||||
"noPreview": "Für diese Datei ist keine Vorschau verfügbar."
|
||||
"noPreview": "Für diese Datei ist keine Vorschau verfügbar.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "Wähle Datei oder Ordner",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Μεταφόρτωση",
|
||||
"openFile": "Άνοιγμα αρχείου",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ταξινόμηση κατά πρόσφατη τροποποίηση",
|
||||
"sortByName": "Ταξινόμηση κατά όνομα",
|
||||
"sortBySize": "Ταξινόμηση κατά μέγεθος",
|
||||
"noPreview": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτό το αρχείο."
|
||||
"noPreview": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτό το αρχείο.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "επιλέξτε αρχείο ή φάκελο",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Subir",
|
||||
"openFile": "Abrir archivo",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Guardar cambios"
|
||||
"saveChanges": "Guardar cambios",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descargar fichero",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordenar por última modificación",
|
||||
"sortByName": "Ordenar por nombre",
|
||||
"sortBySize": "Ordenar por tamaño",
|
||||
"noPreview": "La vista previa no está disponible para este archivo."
|
||||
"noPreview": "La vista previa no está disponible para este archivo.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "seleccionar archivo o carpeta",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "آپلود",
|
||||
"openFile": "باز کردن فایل",
|
||||
"discardChanges": "لغو کردن",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "دانلود فایل",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "مرتب سازی آخرین ویرایش",
|
||||
"sortByName": "مرتب سازی نام",
|
||||
"sortBySize": "مرتب سازی اندازه",
|
||||
"noPreview": "این فایل قابل نمایش نیست"
|
||||
"noPreview": "این فایل قابل نمایش نیست",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "انتخاب فایل یا پوشه",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Importer",
|
||||
"openFile": "Ouvrir le fichier",
|
||||
"discardChanges": "Annuler",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Télécharger le fichier",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Trier par date de modification",
|
||||
"sortByName": "Trier par nom",
|
||||
"sortBySize": "Trier par taille",
|
||||
"noPreview": "L'aperçu n'est pas disponible pour ce fichier."
|
||||
"noPreview": "L'aperçu n'est pas disponible pour ce fichier.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "Sélectionner un fichier ou dossier",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "העלאה",
|
||||
"openFile": "פתח קובץ",
|
||||
"discardChanges": "זריקת השינויים",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "הורד קובץ",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "מיין לפי השינוי האחרון",
|
||||
"sortByName": "מיין לפי שם",
|
||||
"sortBySize": "מיין לפי גודל",
|
||||
"noPreview": "לא זמינה תצוגה מקדימה לקובץ זה"
|
||||
"noPreview": "לא זמינה תצוגה מקדימה לקובץ זה",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "בחר קובץ או תיקייה",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Prenesi",
|
||||
"openFile": "Otvori datoteku",
|
||||
"discardChanges": "Odbaci",
|
||||
"saveChanges": "Spremi promjene"
|
||||
"saveChanges": "Spremi promjene",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Preuzmi Datoteku",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sortiraj po zadnjoj izmjeni",
|
||||
"sortByName": "Sortiraj po nazivu",
|
||||
"sortBySize": "Sortiraj po veličini",
|
||||
"noPreview": "Pregled nije dostupan za ovu datoteku."
|
||||
"noPreview": "Pregled nije dostupan za ovu datoteku.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "odaberi datoteku ili mapu",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Feltöltés",
|
||||
"openFile": "Fájl megnyitása",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Fájl letöltése",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Rendezés utolsó módosítás szerint",
|
||||
"sortByName": "Rendezés név szerint",
|
||||
"sortBySize": "Rendezés méret szerint",
|
||||
"noPreview": "Ehhez a fájlhoz nincs előnézet."
|
||||
"noPreview": "Ehhez a fájlhoz nincs előnézet.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "mappa vagy fájl kijelölése",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Hlaða upp",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Sækja skjal",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Flokka eftir Seinast breytt",
|
||||
"sortByName": "Flokka eftir nafni",
|
||||
"sortBySize": "Flokka eftir stærð",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
"noPreview": "Preview is not available for this file.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "velja skjal eða möppu",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Carica",
|
||||
"openFile": "Apri file",
|
||||
"discardChanges": "Ignora",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Scarica file",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordina per ultima modifica",
|
||||
"sortByName": "Ordina per nome",
|
||||
"sortBySize": "Ordina per dimensione",
|
||||
"noPreview": "L'anteprima non è disponibile per questo file."
|
||||
"noPreview": "L'anteprima non è disponibile per questo file.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "seleziona un file o una cartella",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "アップロード",
|
||||
"openFile": "ファイルを開く",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "ファイルのダウンロード",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "更新日時で並べ替え",
|
||||
"sortByName": "名前で並べ替え",
|
||||
"sortBySize": "サイズで並べ替え",
|
||||
"noPreview": "プレビューはこのファイルでは利用できません"
|
||||
"noPreview": "プレビューはこのファイルでは利用できません",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "ファイルやフォルダーを選択",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "업로드",
|
||||
"openFile": "파일 열기",
|
||||
"discardChanges": "변경 사항 취소",
|
||||
"saveChanges": "변경사항 저장"
|
||||
"saveChanges": "변경사항 저장",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "파일 다운로드",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "수정시간순 정렬",
|
||||
"sortByName": "이름순",
|
||||
"sortBySize": "크기순",
|
||||
"noPreview": "미리 보기가 지원되지 않는 파일 유형입니다."
|
||||
"noPreview": "미리 보기가 지원되지 않는 파일 유형입니다.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "파일이나 디렉토리를 선택해주세요.",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Uploaden",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Bestand downloaden",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sorteren op laatst bewerkt",
|
||||
"sortByName": "Sorteren op naam",
|
||||
"sortBySize": "Sorteren op grootte",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
"noPreview": "Preview is not available for this file.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "selecteer bestand of map",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Last opp",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Slett",
|
||||
"saveChanges": "Lagre Endringane "
|
||||
"saveChanges": "Lagre Endringane ",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Nedlast filen",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sorter etter sist endret",
|
||||
"sortByName": "Sorter etter navn",
|
||||
"sortBySize": "Sorter etter størrelse",
|
||||
"noPreview": "Forhåndsvisning er ikkje tilgjengeleg for denne filen."
|
||||
"noPreview": "Forhåndsvisning er ikkje tilgjengeleg for denne filen.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "velg fil eller katalog",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Wyślij",
|
||||
"openFile": "Otwórz plik",
|
||||
"discardChanges": "Odrzuć",
|
||||
"saveChanges": "Zapisz zmiany"
|
||||
"saveChanges": "Zapisz zmiany",
|
||||
"editAsText": "Edytuj jako tekst"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Pobierz plik",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sortuj wg ostatniej modyfikacji",
|
||||
"sortByName": "Sortuj wg nazwy",
|
||||
"sortBySize": "Sortuj wg rozmiaru",
|
||||
"noPreview": "Podgląd tego pliku jest niedostępny."
|
||||
"noPreview": "Podgląd tego pliku jest niedostępny.",
|
||||
"csvTooLarge": "Plik CSV jest za duży do podglądu (>5 MB). Pobierz, aby wyświetlić.",
|
||||
"csvLoadFailed": "Nie udało się załadować pliku CSV."
|
||||
},
|
||||
"help": {
|
||||
"click": "zaznacz plik lub folder",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Enviar",
|
||||
"openFile": "Abrir",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Baixar arquivo",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordenar pela última modificação",
|
||||
"sortByName": "Ordenar pelo nome",
|
||||
"sortBySize": "Ordenar pelo tamanho",
|
||||
"noPreview": "Pré-visualização não disponível para este arquivo."
|
||||
"noPreview": "Pré-visualização não disponível para este arquivo.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "selecionar pasta ou arquivo",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Enviar",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarregar ficheiro",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordenar pela última alteração",
|
||||
"sortByName": "Ordenar pelo nome",
|
||||
"sortBySize": "Ordenar pelo tamanho",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
"noPreview": "Preview is not available for this file.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "selecionar pasta ou ficheiro",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Încarcă",
|
||||
"openFile": "Open file",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarcă fișier",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Ordonează dup ultima modificare",
|
||||
"sortByName": "Ordonează după nume",
|
||||
"sortBySize": "Ordonează după dimensiune",
|
||||
"noPreview": "Preview is not available for this file."
|
||||
"noPreview": "Preview is not available for this file.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "alege fișier sau director",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Загрузить",
|
||||
"openFile": "Открыть файл",
|
||||
"discardChanges": "Отказаться",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Скачать файл",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Сортировка по дате изменения",
|
||||
"sortByName": "Сортировка по имени",
|
||||
"sortBySize": "Сортировка по размеру",
|
||||
"noPreview": "Предварительный просмотр для этого файла недоступен."
|
||||
"noPreview": "Предварительный просмотр для этого файла недоступен.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "выбрать файл или каталог",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Nahrať",
|
||||
"openFile": "Otvoriť súbor",
|
||||
"discardChanges": "Zahodiť",
|
||||
"saveChanges": "Uložiť zmeny"
|
||||
"saveChanges": "Uložiť zmeny",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stiahnuť súbor",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Zoradiť podľa dátumu",
|
||||
"sortByName": "Zoradiť podľa názvu",
|
||||
"sortBySize": "Zoradiť podľa veľkosti",
|
||||
"noPreview": "Pre tento súbor nie je dostupný náhľad."
|
||||
"noPreview": "Pre tento súbor nie je dostupný náhľad.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "vyberie súbor alebo priečinok",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Ladda upp",
|
||||
"openFile": "Öppna fil",
|
||||
"discardChanges": "Förkasta",
|
||||
"saveChanges": "Spara ändringar"
|
||||
"saveChanges": "Spara ändringar",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Ladda ner fil",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sortera på senast ändrad",
|
||||
"sortByName": "Sortera på namn",
|
||||
"sortBySize": "Sortera på storlek",
|
||||
"noPreview": "Förhandsvisning är inte tillgänglig för denna fil."
|
||||
"noPreview": "Förhandsvisning är inte tillgänglig för denna fil.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "välj fil eller mapp",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Yükle",
|
||||
"openFile": "Dosyayı aç",
|
||||
"discardChanges": "Discard",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Dosyayı indir",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Güncelleme tarihine göre sırala",
|
||||
"sortByName": "İsme göre sırala",
|
||||
"sortBySize": "Boyuta göre sırala",
|
||||
"noPreview": "Bu dosya için önizleme aktif değil"
|
||||
"noPreview": "Bu dosya için önizleme aktif değil",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "dosya veya klasör seçin",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Вивантажити",
|
||||
"openFile": "Відкрити файл",
|
||||
"discardChanges": "Скасувати",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Завантажити файл",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Сортувати за останнім зміненням",
|
||||
"sortByName": "Сортувати за іменем",
|
||||
"sortBySize": "Сортувати за розміром",
|
||||
"noPreview": "Попередній перегляд для цього файлу недоступний."
|
||||
"noPreview": "Попередній перегляд для цього файлу недоступний.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "вибрати файл чи каталог",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "Tải lên",
|
||||
"openFile": "Mở tệp",
|
||||
"discardChanges": "Hủy bỏ thay đổi",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Tải xuống tệp tin",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "Sắp xếp theo ngày sửa đổi",
|
||||
"sortByName": "Sắp xếp theo tên",
|
||||
"sortBySize": "Sắp xếp theo kích thước",
|
||||
"noPreview": "Không có bản xem trước cho tập tin này."
|
||||
"noPreview": "Không có bản xem trước cho tập tin này.",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "chọn tập tin hoặc thư mục",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "上传",
|
||||
"openFile": "打开文件",
|
||||
"discardChanges": "放弃更改",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "下载文件",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "按最后修改时间排序",
|
||||
"sortByName": "按名称排序",
|
||||
"sortBySize": "按大小排序",
|
||||
"noPreview": "此文件无法预览。"
|
||||
"noPreview": "此文件无法预览。",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "选择文件或文件夹",
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
"upload": "上傳",
|
||||
"openFile": "開啟檔案",
|
||||
"discardChanges": "放棄變更",
|
||||
"saveChanges": "Save changes"
|
||||
"saveChanges": "Save changes",
|
||||
"editAsText": "Edit as Text"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "下載檔案",
|
||||
@@ -75,7 +76,9 @@
|
||||
"sortByLastModified": "按最後修改時間排序",
|
||||
"sortByName": "按名稱排序",
|
||||
"sortBySize": "按大小排序",
|
||||
"noPreview": "此檔案無法預覽。"
|
||||
"noPreview": "此檔案無法預覽。",
|
||||
"csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.",
|
||||
"csvLoadFailed": "Failed to load CSV file."
|
||||
},
|
||||
"help": {
|
||||
"click": "選擇檔案或目錄",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useAuthStore } from "@/stores/auth";
|
||||
import router from "@/router";
|
||||
import type { JwtPayload } from "jwt-decode";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { baseURL, noAuth } from "./constants";
|
||||
import { authMethod, baseURL, noAuth, logoutPage } from "./constants";
|
||||
import { StatusError } from "@/api/utils";
|
||||
import { setSafeTimeout } from "@/api/utils";
|
||||
|
||||
@@ -18,6 +18,12 @@ export function parseToken(token: string) {
|
||||
authStore.jwt = token;
|
||||
authStore.setUser(data.user);
|
||||
|
||||
// proxy auth with custom logout subject to unknown external timeout
|
||||
if (logoutPage !== "/login" && authMethod === "proxy") {
|
||||
console.warn("idle timeout disabled with proxy auth and custom logout");
|
||||
return;
|
||||
}
|
||||
|
||||
if (authStore.logoutTimer) {
|
||||
clearTimeout(authStore.logoutTimer);
|
||||
}
|
||||
@@ -118,6 +124,8 @@ export function logout(reason?: string) {
|
||||
localStorage.setItem("jwt", "");
|
||||
if (noAuth) {
|
||||
window.location.reload();
|
||||
} else if (logoutPage !== "/login") {
|
||||
document.location.href = `${logoutPage}`;
|
||||
} else {
|
||||
if (typeof reason === "string" && reason.trim() !== "") {
|
||||
router.push({
|
||||
|
||||
@@ -10,6 +10,7 @@ const version: string = window.FileBrowser.Version;
|
||||
const logoURL = `${staticURL}/img/logo.svg`;
|
||||
const noAuth: boolean = window.FileBrowser.NoAuth;
|
||||
const authMethod = window.FileBrowser.AuthMethod;
|
||||
const logoutPage: string = window.FileBrowser.LogoutPage;
|
||||
const loginPage: boolean = window.FileBrowser.LoginPage;
|
||||
const theme: UserTheme = window.FileBrowser.Theme;
|
||||
const enableThumbs: boolean = window.FileBrowser.EnableThumbs;
|
||||
@@ -32,6 +33,7 @@ export {
|
||||
version,
|
||||
noAuth,
|
||||
authMethod,
|
||||
logoutPage,
|
||||
loginPage,
|
||||
theme,
|
||||
enableThumbs,
|
||||
|
||||
@@ -186,7 +186,7 @@ const handlePageChange = (event: BeforeUnloadEvent) => {
|
||||
}
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
const save = async (throwError?: boolean) => {
|
||||
const button = "save";
|
||||
buttons.loading("save");
|
||||
|
||||
@@ -197,6 +197,7 @@ const save = async () => {
|
||||
} catch (e: any) {
|
||||
buttons.done(button);
|
||||
$showError(e);
|
||||
if (throwError) throw e;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -223,8 +224,10 @@ const close = () => {
|
||||
finishClose();
|
||||
},
|
||||
saveAction: async () => {
|
||||
await save();
|
||||
finishClose();
|
||||
try {
|
||||
await save(true);
|
||||
finishClose();
|
||||
} catch {}
|
||||
},
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -12,7 +12,9 @@ import (
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/golang-jwt/jwt/v5/request"
|
||||
|
||||
fbAuth "github.com/filebrowser/filebrowser/v2/auth"
|
||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
@@ -61,6 +63,22 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
||||
return "", request.ErrNoTokenInRequest
|
||||
}
|
||||
|
||||
func renewableErr(err error, d *data) bool {
|
||||
if d.settings.AuthMethod != fbAuth.MethodProxyAuth || err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if d.settings.LogoutPage == settings.DefaultLogoutPage {
|
||||
return false
|
||||
}
|
||||
|
||||
if !errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func withUser(fn handleFunc) handleFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
keyFunc := func(_ *jwt.Token) (interface{}, error) {
|
||||
@@ -68,13 +86,9 @@ func withUser(fn handleFunc) handleFunc {
|
||||
}
|
||||
|
||||
var tk authToken
|
||||
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk))
|
||||
if err != nil || !token.Valid {
|
||||
return http.StatusUnauthorized, nil
|
||||
}
|
||||
|
||||
err = jwt.NewValidator(jwt.WithExpirationRequired()).Validate(tk)
|
||||
if err != nil {
|
||||
p := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithExpirationRequired())
|
||||
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk), request.WithParser(p))
|
||||
if (err != nil || !token.Valid) && !renewableErr(err, d) {
|
||||
return http.StatusUnauthorized, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||
"Signup": d.settings.Signup,
|
||||
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
|
||||
"AuthMethod": d.settings.AuthMethod,
|
||||
"LogoutPage": d.settings.LogoutPage,
|
||||
"LoginPage": auther.LoginPage(),
|
||||
"CSS": false,
|
||||
"ReCaptcha": false,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const DefaultUsersHomeBasePath = "/users"
|
||||
const DefaultLogoutPage = "/login"
|
||||
const DefaultMinimumPasswordLength = 12
|
||||
const DefaultFileMode = 0640
|
||||
const DefaultDirMode = 0750
|
||||
@@ -27,6 +28,7 @@ type Settings struct {
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
LogoutPage string `json:"logoutPage"`
|
||||
Branding Branding `json:"branding"`
|
||||
Tus Tus `json:"tus"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
|
||||
@@ -30,24 +30,34 @@ func (s *Storage) Get() (*Settings, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if set.UserHomeBasePath == "" {
|
||||
set.UserHomeBasePath = DefaultUsersHomeBasePath
|
||||
}
|
||||
|
||||
if set.LogoutPage == "" {
|
||||
set.LogoutPage = DefaultLogoutPage
|
||||
}
|
||||
|
||||
if set.MinimumPasswordLength == 0 {
|
||||
set.MinimumPasswordLength = DefaultMinimumPasswordLength
|
||||
}
|
||||
|
||||
if set.Tus == (Tus{}) {
|
||||
set.Tus = Tus{
|
||||
ChunkSize: DefaultTusChunkSize,
|
||||
RetryCount: DefaultTusRetryCount,
|
||||
}
|
||||
}
|
||||
|
||||
if set.FileMode == 0 {
|
||||
set.FileMode = DefaultFileMode
|
||||
}
|
||||
|
||||
if set.DirMode == 0 {
|
||||
set.DirMode = DefaultDirMode
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ filebrowser config init [flags]
|
||||
-a, --address string address to listen on (default "127.0.0.1")
|
||||
--auth.command string command for auth.method=hook
|
||||
--auth.header string HTTP header for auth.method=proxy
|
||||
--auth.logoutPage string url of custom logout page
|
||||
--auth.method string authentication type (default "json")
|
||||
-b, --baseURL string base url
|
||||
--branding.color string set the theme color
|
||||
|
||||
@@ -18,6 +18,7 @@ filebrowser config set [flags]
|
||||
-a, --address string address to listen on (default "127.0.0.1")
|
||||
--auth.command string command for auth.method=hook
|
||||
--auth.header string HTTP header for auth.method=proxy
|
||||
--auth.logoutPage string url of custom logout page
|
||||
--auth.method string authentication type (default "json")
|
||||
-b, --baseURL string base url
|
||||
--branding.color string set the theme color
|
||||
|
||||
@@ -32,7 +32,7 @@ PROJECT_NAME=$(shell basename "${ROOT}")
|
||||
# - pkg/version/current.go
|
||||
#
|
||||
# Use `tools/bump_version.sh` script to change all those files at one shot.
|
||||
VERSION="3.24.1"
|
||||
VERSION="3.25.0"
|
||||
|
||||
# With .ONESHELL, each recipe is executed in a single shell instance.
|
||||
# This allows `cd` to affect subsequent commands in the same recipe.
|
||||
|
||||
72
mieru/apis/common/conn.go
Normal file
72
mieru/apis/common/conn.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2025 mieru authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HierarchyConn closes all child connections when this connection is closed.
|
||||
type HierarchyConn interface {
|
||||
net.Conn
|
||||
|
||||
// Add attach a child connection to this connection.
|
||||
// The child connection is closed when this connection close.
|
||||
Add(conn net.Conn)
|
||||
}
|
||||
|
||||
type hierarchyConn struct {
|
||||
net.Conn
|
||||
subConnetions []HierarchyConn
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
_ HierarchyConn = (*hierarchyConn)(nil)
|
||||
_ UserContext = (*hierarchyConn)(nil)
|
||||
)
|
||||
|
||||
func (h *hierarchyConn) Close() error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
for _, sub := range h.subConnetions {
|
||||
if sub != nil {
|
||||
sub.Close()
|
||||
}
|
||||
}
|
||||
return h.Conn.Close()
|
||||
}
|
||||
|
||||
func (h *hierarchyConn) Add(conn net.Conn) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.subConnetions = append(h.subConnetions, WrapHierarchyConn(conn))
|
||||
}
|
||||
|
||||
func (h *hierarchyConn) UserName() string {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if userCtx, ok := h.Conn.(UserContext); ok {
|
||||
return userCtx.UserName()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WrapHierarchyConn wraps an existing connection with HierarchyConn.
|
||||
func WrapHierarchyConn(conn net.Conn) HierarchyConn {
|
||||
return &hierarchyConn{Conn: conn}
|
||||
}
|
||||
43
mieru/apis/common/conn_test.go
Normal file
43
mieru/apis/common/conn_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2025 mieru authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/enfein/mieru/v3/apis/common"
|
||||
)
|
||||
|
||||
type counterCloser struct {
|
||||
net.Conn
|
||||
Counter *int
|
||||
}
|
||||
|
||||
func (cc counterCloser) Close() error {
|
||||
*cc.Counter = *cc.Counter + 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHierarchyConn(t *testing.T) {
|
||||
counter := 0
|
||||
parent := common.WrapHierarchyConn(counterCloser{Conn: nil, Counter: &counter})
|
||||
parent.Add(counterCloser{Conn: nil, Counter: &counter})
|
||||
parent.Close()
|
||||
if counter != 2 {
|
||||
t.Errorf("counter = %d, want %d", counter, 2)
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,8 @@ type ServerNetworkService interface {
|
||||
// Accept accepts a new proxy connection from a client.
|
||||
// It returns the proxy connection and the socks5 request sent by the client.
|
||||
// Additional handshake is required with the returned proxy connection.
|
||||
//
|
||||
// The returned proxy connection implements UserContext interface.
|
||||
Accept() (net.Conn, *model.Request, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apicommon "github.com/enfein/mieru/v3/apis/common"
|
||||
"github.com/enfein/mieru/v3/apis/model"
|
||||
"github.com/enfein/mieru/v3/pkg/appctl/appctlcommon"
|
||||
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
@@ -127,6 +128,9 @@ func (ms *mieruServer) Accept() (net.Conn, *model.Request, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, ok := conn.(apicommon.UserContext); !ok {
|
||||
return nil, nil, fmt.Errorf("internal error: connection doesn't implement UserContext interface")
|
||||
}
|
||||
|
||||
common.SetReadTimeout(conn, 10*time.Second)
|
||||
defer func() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: mieru
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: mieru
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Mieru proxy client
|
||||
License: GPLv3+
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: mieru
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: arm64
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: mieru
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Mieru proxy client
|
||||
License: GPLv3+
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: mita
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: mita
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Mieru proxy server
|
||||
License: GPLv3+
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: mita
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: arm64
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: mita
|
||||
Version: 3.24.1
|
||||
Version: 3.25.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Mieru proxy server
|
||||
License: GPLv3+
|
||||
|
||||
@@ -18,32 +18,32 @@ Or you can manually install and configure proxy server using the steps below.
|
||||
|
||||
```sh
|
||||
# Debian / Ubuntu - X86_64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_amd64.deb
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_amd64.deb
|
||||
|
||||
# Debian / Ubuntu - ARM 64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_arm64.deb
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_arm64.deb
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - X86_64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.x86_64.rpm
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.x86_64.rpm
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - ARM 64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.aarch64.rpm
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.aarch64.rpm
|
||||
```
|
||||
|
||||
## Install mita package
|
||||
|
||||
```sh
|
||||
# Debian / Ubuntu - X86_64
|
||||
sudo dpkg -i mita_3.24.1_amd64.deb
|
||||
sudo dpkg -i mita_3.25.0_amd64.deb
|
||||
|
||||
# Debian / Ubuntu - ARM 64
|
||||
sudo dpkg -i mita_3.24.1_arm64.deb
|
||||
sudo dpkg -i mita_3.25.0_arm64.deb
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - X86_64
|
||||
sudo rpm -Uvh --force mita-3.24.1-1.x86_64.rpm
|
||||
sudo rpm -Uvh --force mita-3.25.0-1.x86_64.rpm
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - ARM 64
|
||||
sudo rpm -Uvh --force mita-3.24.1-1.aarch64.rpm
|
||||
sudo rpm -Uvh --force mita-3.25.0-1.aarch64.rpm
|
||||
```
|
||||
|
||||
Those instructions can also be used to upgrade the version of mita software package.
|
||||
|
||||
@@ -18,32 +18,32 @@ sudo python3 setup.py --lang=zh
|
||||
|
||||
```sh
|
||||
# Debian / Ubuntu - X86_64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_amd64.deb
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_amd64.deb
|
||||
|
||||
# Debian / Ubuntu - ARM 64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_arm64.deb
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita_3.25.0_arm64.deb
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - X86_64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.x86_64.rpm
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.x86_64.rpm
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - ARM 64
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.aarch64.rpm
|
||||
curl -LSO https://github.com/enfein/mieru/releases/download/v3.25.0/mita-3.25.0-1.aarch64.rpm
|
||||
```
|
||||
|
||||
## 安装 mita 软件包
|
||||
|
||||
```sh
|
||||
# Debian / Ubuntu - X86_64
|
||||
sudo dpkg -i mita_3.24.1_amd64.deb
|
||||
sudo dpkg -i mita_3.25.0_amd64.deb
|
||||
|
||||
# Debian / Ubuntu - ARM 64
|
||||
sudo dpkg -i mita_3.24.1_arm64.deb
|
||||
sudo dpkg -i mita_3.25.0_arm64.deb
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - X86_64
|
||||
sudo rpm -Uvh --force mita-3.24.1-1.x86_64.rpm
|
||||
sudo rpm -Uvh --force mita-3.25.0-1.x86_64.rpm
|
||||
|
||||
# RedHat / CentOS / Rocky Linux - ARM 64
|
||||
sudo rpm -Uvh --force mita-3.24.1-1.aarch64.rpm
|
||||
sudo rpm -Uvh --force mita-3.25.0-1.aarch64.rpm
|
||||
```
|
||||
|
||||
上述指令也可以用来升级 mita 软件包的版本。
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package appctlcommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -96,10 +95,7 @@ func NewClientMuxFromProfile(activeProfile *pb.ClientProfile, dialer apicommon.D
|
||||
// Set DNS resolver.
|
||||
// If DNS resolver is not provided, disable DNS resolution.
|
||||
// Connection to a domain name endpoint may fail.
|
||||
enableDNS := false
|
||||
if resolver != nil {
|
||||
enableDNS = true
|
||||
} else {
|
||||
if resolver == nil {
|
||||
resolver = apicommon.NilDNSResolver{}
|
||||
}
|
||||
mux.SetResolver(resolver)
|
||||
@@ -142,16 +138,6 @@ func NewClientMuxFromProfile(activeProfile *pb.ClientProfile, dialer apicommon.D
|
||||
var proxyIP net.IP
|
||||
if serverInfo.GetDomainName() != "" {
|
||||
proxyHost = serverInfo.GetDomainName()
|
||||
if enableDNS {
|
||||
proxyIPs, err := resolver.LookupIP(context.Background(), "ip", proxyHost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(stderror.LookupIPFailedErr, err)
|
||||
}
|
||||
if len(proxyIPs) == 0 {
|
||||
return nil, fmt.Errorf(stderror.IPAddressNotFound, proxyHost)
|
||||
}
|
||||
proxyIP = proxyIPs[0]
|
||||
}
|
||||
} else {
|
||||
proxyHost = serverInfo.GetIpAddress()
|
||||
proxyIP = net.ParseIP(proxyHost)
|
||||
|
||||
@@ -18,8 +18,6 @@ package common
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -62,57 +60,3 @@ func RoundTrip(ctx context.Context, rw io.ReadWriter, req []byte, maxRespSize in
|
||||
resp = resp[:n]
|
||||
return
|
||||
}
|
||||
|
||||
// HierarchyConn closes sub-connections when this connection is closed.
|
||||
type HierarchyConn interface {
|
||||
net.Conn
|
||||
|
||||
// AddSubConnection attach a child connection to this connection.
|
||||
// The child connection is closed when this connection close.
|
||||
AddSubConnection(conn net.Conn)
|
||||
}
|
||||
|
||||
type hierarchyConn struct {
|
||||
net.Conn
|
||||
subConnetions []HierarchyConn
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
_ HierarchyConn = (*hierarchyConn)(nil)
|
||||
_ UserContext = (*hierarchyConn)(nil)
|
||||
)
|
||||
|
||||
func (h *hierarchyConn) Close() error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
for _, sub := range h.subConnetions {
|
||||
if sub != nil {
|
||||
sub.Close()
|
||||
}
|
||||
}
|
||||
return h.Conn.Close()
|
||||
}
|
||||
|
||||
func (h *hierarchyConn) AddSubConnection(conn net.Conn) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.subConnetions == nil {
|
||||
h.subConnetions = make([]HierarchyConn, 0)
|
||||
}
|
||||
h.subConnetions = append(h.subConnetions, WrapHierarchyConn(conn))
|
||||
}
|
||||
|
||||
func (h *hierarchyConn) UserName() string {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if userCtx, ok := h.Conn.(UserContext); ok {
|
||||
return userCtx.UserName()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WrapHierarchyConn wraps an existing connection with HierarchyConn.
|
||||
func WrapHierarchyConn(conn net.Conn) HierarchyConn {
|
||||
return &hierarchyConn{Conn: conn}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
crand "crypto/rand"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -37,23 +36,3 @@ func TestReadAllAndDiscard(t *testing.T) {
|
||||
t.Errorf("buf.Len() = %d, want 0", buf.Len())
|
||||
}
|
||||
}
|
||||
|
||||
type counterCloser struct {
|
||||
net.Conn
|
||||
Counter *int
|
||||
}
|
||||
|
||||
func (cc counterCloser) Close() error {
|
||||
*cc.Counter = *cc.Counter + 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHierarchyConn(t *testing.T) {
|
||||
counter := 0
|
||||
parent := WrapHierarchyConn(counterCloser{Conn: nil, Counter: &counter})
|
||||
parent.AddSubConnection(counterCloser{Conn: nil, Counter: &counter})
|
||||
parent.Close()
|
||||
if counter != 2 {
|
||||
t.Errorf("counter = %d, want %d", counter, 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +617,7 @@ func (m *Mux) newUnderlay(ctx context.Context) (Underlay, error) {
|
||||
block.SetBlockContext(cipher.BlockContext{
|
||||
UserName: m.username,
|
||||
})
|
||||
underlay, err = NewStreamUnderlay(ctx, m.dialer, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block)
|
||||
underlay, err = NewStreamUnderlay(ctx, m.dialer, m.resolver, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewTCPUnderlay() failed: %v", err)
|
||||
}
|
||||
@@ -629,7 +629,7 @@ func (m *Mux) newUnderlay(ctx context.Context) (Underlay, error) {
|
||||
block.SetBlockContext(cipher.BlockContext{
|
||||
UserName: m.username,
|
||||
})
|
||||
underlay, err = NewPacketUnderlay(ctx, m.packetDialer, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block, m.resolver)
|
||||
underlay, err = NewPacketUnderlay(ctx, m.packetDialer, m.resolver, p.RemoteAddr().Network(), p.RemoteAddr().String(), p.MTU(), block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewUDPUnderlay() failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
apicommon "github.com/enfein/mieru/v3/apis/common"
|
||||
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"github.com/enfein/mieru/v3/pkg/cipher"
|
||||
"github.com/enfein/mieru/v3/pkg/common"
|
||||
@@ -109,7 +110,7 @@ type Session struct {
|
||||
status statusCode // session status
|
||||
|
||||
users map[string]*appctlpb.User // all registered users, only used by server
|
||||
userName string // user that owns this session, only used by server
|
||||
userName atomic.Pointer[string] // user that owns this session, only used by server
|
||||
|
||||
ready chan struct{} // indicate the session is ready to use
|
||||
closeRequested atomic.Bool // the session is being closed or has been closed
|
||||
@@ -155,8 +156,8 @@ var (
|
||||
// Session implements net.Conn interface.
|
||||
_ net.Conn = (*Session)(nil)
|
||||
|
||||
// Session implements common.UserContext interface.
|
||||
_ common.UserContext = (*Session)(nil)
|
||||
// Session implements UserContext interface.
|
||||
_ apicommon.UserContext = (*Session)(nil)
|
||||
)
|
||||
|
||||
// NewSession creates a new session.
|
||||
@@ -403,7 +404,10 @@ func (s *Session) SetWriteDeadline(t time.Time) error {
|
||||
// UserName returns the user that owns this session,
|
||||
// only if this is a server session.
|
||||
func (s *Session) UserName() string {
|
||||
return s.userName
|
||||
if p := s.userName.Load(); p != nil {
|
||||
return *p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ToSessionInfo creates related SessionInfo protobuf object.
|
||||
@@ -905,8 +909,9 @@ func (s *Session) input(seg *segment) error {
|
||||
}
|
||||
|
||||
s.block.Store(&seg.block)
|
||||
if s.userName == "" && seg.block.BlockContext().UserName != "" {
|
||||
s.userName = seg.block.BlockContext().UserName
|
||||
if s.UserName() == "" && seg.block.BlockContext().UserName != "" {
|
||||
userName := seg.block.BlockContext().UserName
|
||||
s.userName.Store(&userName)
|
||||
}
|
||||
|
||||
// Register server per user metrics.
|
||||
@@ -1026,14 +1031,14 @@ func (s *Session) inputData(seg *segment) error {
|
||||
// Server needs to send open session response.
|
||||
// Check user quota if we can identify the user.
|
||||
s.oLock.Lock()
|
||||
if s.userName != "" {
|
||||
quotaOK, err := s.checkQuota(s.userName)
|
||||
if userName := s.UserName(); userName != "" {
|
||||
quotaOK, err := s.checkQuota(userName)
|
||||
if err != nil {
|
||||
log.Debugf("%v checkQuota() failed: %v", s, err)
|
||||
}
|
||||
if !quotaOK {
|
||||
s.status = statusQuotaExhausted
|
||||
log.Debugf("Closing %v because user %s used all the quota", s, s.userName)
|
||||
log.Debugf("Closing %v because user %s used all the quota", s, userName)
|
||||
s.oLock.Unlock()
|
||||
s.Close()
|
||||
return nil
|
||||
|
||||
@@ -66,7 +66,7 @@ var _ Underlay = &PacketUnderlay{}
|
||||
// "block" is the block encryption algorithm to encrypt packets.
|
||||
//
|
||||
// This function is only used by proxy client.
|
||||
func NewPacketUnderlay(ctx context.Context, packetDialer apicommon.PacketDialer, network, addr string, mtu int, block cipher.BlockCipher, resolver apicommon.DNSResolver) (*PacketUnderlay, error) {
|
||||
func NewPacketUnderlay(ctx context.Context, packetDialer apicommon.PacketDialer, resolver apicommon.DNSResolver, network, addr string, mtu int, block cipher.BlockCipher) (*PacketUnderlay, error) {
|
||||
switch network {
|
||||
case "udp", "udp4", "udp6":
|
||||
default:
|
||||
@@ -75,7 +75,7 @@ func NewPacketUnderlay(ctx context.Context, packetDialer apicommon.PacketDialer,
|
||||
if !block.IsStateless() {
|
||||
return nil, fmt.Errorf("packet underlay block cipher must be stateless")
|
||||
}
|
||||
remoteAddr, err := apicommon.ResolveUDPAddr(resolver, "udp", addr)
|
||||
remoteAddr, err := apicommon.ResolveUDPAddr(resolver, network, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ResolveUDPAddr() failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ var _ Underlay = &StreamUnderlay{}
|
||||
// "block" is the block encryption algorithm to encrypt packets.
|
||||
//
|
||||
// This function is only used by proxy client.
|
||||
func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, network, addr string, mtu int, block cipher.BlockCipher) (*StreamUnderlay, error) {
|
||||
func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, resolver apicommon.DNSResolver, network, addr string, mtu int, block cipher.BlockCipher) (*StreamUnderlay, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
default:
|
||||
@@ -72,7 +72,12 @@ func NewStreamUnderlay(ctx context.Context, dialer apicommon.Dialer, network, ad
|
||||
if block.IsStateless() {
|
||||
return nil, fmt.Errorf("stream underlay block cipher must be stateful")
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, network, addr)
|
||||
remoteAddr, err := apicommon.ResolveTCPAddr(resolver, network, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ResolveTCPAddr() failed: %w", err)
|
||||
}
|
||||
|
||||
conn, err := dialer.DialContext(ctx, network, remoteAddr.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DialContext() failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func (s *Server) Serve(l net.Listener) error {
|
||||
|
||||
// ServeConn is used to serve a single connection.
|
||||
func (s *Server) ServeConn(conn net.Conn) error {
|
||||
conn = common.WrapHierarchyConn(conn)
|
||||
conn = apicommon.WrapHierarchyConn(conn)
|
||||
defer conn.Close()
|
||||
log.Debugf("socks5 server starts to serve connection [%v - %v]", conn.LocalAddr(), conn.RemoteAddr())
|
||||
|
||||
@@ -227,7 +227,7 @@ func (s *Server) clientServeConn(conn net.Conn) error {
|
||||
return common.BidiCopy(conn, proxyConn)
|
||||
}
|
||||
log.Debugf("UDP association is listening on %v", udpAssociateConn.LocalAddr())
|
||||
conn.(common.HierarchyConn).AddSubConnection(udpAssociateConn)
|
||||
conn.(apicommon.HierarchyConn).Add(udpAssociateConn)
|
||||
go func() {
|
||||
common.ReadAllAndDiscard(conn)
|
||||
conn.Close()
|
||||
@@ -262,18 +262,15 @@ func (s *Server) serverServeConn(conn net.Conn) error {
|
||||
Protocol: appctlpb.ProxyProtocol_SOCKS5_PROXY_PROTOCOL,
|
||||
Data: request.Raw,
|
||||
}
|
||||
if userCtx, ok := conn.(common.UserContext); ok {
|
||||
userName := userCtx.UserName()
|
||||
if userName == "" {
|
||||
log.Debugf("Failed to determine user name from the connection")
|
||||
} else {
|
||||
log.Debugf("User %q initiated the connection", userName)
|
||||
egressInput.Env = map[string]string{
|
||||
"user": userName,
|
||||
}
|
||||
}
|
||||
userCtx := conn.(apicommon.UserContext)
|
||||
userName := userCtx.UserName()
|
||||
if userName == "" {
|
||||
log.Debugf("Failed to determine user name from the connection")
|
||||
} else {
|
||||
log.Errorf("%T doesn't implement common.UserContext interface", conn)
|
||||
log.Debugf("User %q initiated the connection", userName)
|
||||
egressInput.Env = map[string]string{
|
||||
"user": userName,
|
||||
}
|
||||
}
|
||||
action := s.FindAction(ctx, egressInput)
|
||||
if action.Action == appctlpb.EgressAction_PROXY {
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
package version
|
||||
|
||||
const (
|
||||
AppVersion = "3.24.1"
|
||||
AppVersion = "3.25.0"
|
||||
)
|
||||
|
||||
@@ -102,11 +102,16 @@ func main() {
|
||||
}
|
||||
|
||||
func handleOneProxyConn(proxyConn net.Conn, req *model.Request) {
|
||||
if *debug {
|
||||
fmt.Printf("Received %v\n", req)
|
||||
}
|
||||
defer proxyConn.Close()
|
||||
|
||||
userCtx := proxyConn.(apicommon.UserContext)
|
||||
if userCtx.UserName() == "" {
|
||||
panic("User name is empty")
|
||||
}
|
||||
if *debug {
|
||||
fmt.Printf("Received %v from %s\n", req, userCtx.UserName())
|
||||
}
|
||||
|
||||
var isTCP, isUDP bool
|
||||
switch req.Command {
|
||||
case constant.Socks5ConnectCmd:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"domainName": "localhost",
|
||||
"ipAddress": "127.0.0.1",
|
||||
"portBindings": [
|
||||
{
|
||||
"port": 8966,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"domainName": "localhost",
|
||||
"ipAddress": "127.0.0.1",
|
||||
"portBindings": [
|
||||
{
|
||||
"port": 8966,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"domainName": "localhost",
|
||||
"ipAddress": "127.0.0.1",
|
||||
"portBindings": [
|
||||
{
|
||||
"port": 8966,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"domainName": "localhost",
|
||||
"ipAddress": "127.0.0.1",
|
||||
"portBindings": [
|
||||
{
|
||||
"port": 8966,
|
||||
|
||||
@@ -8,7 +8,7 @@ mode: rule
|
||||
proxies:
|
||||
- name: mieru
|
||||
type: mieru
|
||||
server: 127.0.0.1
|
||||
server: localhost
|
||||
port-range: 8964-8965
|
||||
transport: TCP
|
||||
udp: true
|
||||
|
||||
@@ -8,7 +8,7 @@ mode: rule
|
||||
proxies:
|
||||
- name: mieru
|
||||
type: mieru
|
||||
server: 127.0.0.1
|
||||
server: localhost
|
||||
port-range: 8964-8965
|
||||
transport: TCP
|
||||
udp: true
|
||||
|
||||
@@ -8,7 +8,7 @@ mode: rule
|
||||
proxies:
|
||||
- name: mieru
|
||||
type: mieru
|
||||
server: 127.0.0.1
|
||||
server: localhost
|
||||
port-range: 8964-8965
|
||||
transport: UDP
|
||||
udp: true
|
||||
|
||||
@@ -8,7 +8,7 @@ mode: rule
|
||||
proxies:
|
||||
- name: mieru
|
||||
type: mieru
|
||||
server: 127.0.0.1
|
||||
server: localhost
|
||||
port-range: 8964-8965
|
||||
transport: UDP
|
||||
udp: true
|
||||
|
||||
@@ -35,16 +35,16 @@ sleep 1
|
||||
./mita run &
|
||||
sleep 1
|
||||
|
||||
# Run server test.
|
||||
echo "========== BEGIN OF SERVER TEST =========="
|
||||
./test_server.sh
|
||||
echo "========== END OF SERVER TEST =========="
|
||||
|
||||
# Run client test.
|
||||
echo "========== BEGIN OF CLIENT TEST =========="
|
||||
./test_client.sh
|
||||
echo "========== END OF CLIENT TEST =========="
|
||||
|
||||
# Run server test.
|
||||
echo "========== BEGIN OF SERVER TEST =========="
|
||||
./test_server.sh
|
||||
echo "========== END OF SERVER TEST =========="
|
||||
|
||||
echo "Test is successful."
|
||||
sleep 1
|
||||
exit 0
|
||||
|
||||
@@ -32,11 +32,11 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.9
|
||||
github.com/metacubex/sing-tun v0.4.10
|
||||
github.com/metacubex/sing-vmess v0.2.4
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
|
||||
github.com/metacubex/utls v1.8.3
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||
|
||||
@@ -131,16 +131,16 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
|
||||
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.10 h1:DllQTERAcqQyiEl4L/R7Ia0jCiSzZzikw2kL8N85p0E=
|
||||
github.com/metacubex/sing-tun v0.4.10/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
|
||||
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s=
|
||||
github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
|
||||
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=filebrowser
|
||||
PKG_VERSION:=2.49.0
|
||||
PKG_VERSION:=2.50.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v${PKG_VERSION}?
|
||||
PKG_HASH:=5f35beedf818feef315d84222dd957b8e10fab65a80a7d6ff10e22a31a11722f
|
||||
PKG_HASH:=5947c8a8c7c8df2b2646953cfa1fdee9efac8b4415a368074acab94eacc56fd7
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=25.11.27
|
||||
PKG_VERSION:=25.12.1
|
||||
PKG_RELEASE:=1
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js?v=25.11.27"></script>
|
||||
|
||||
<style>
|
||||
table th, .table .th {
|
||||
@@ -54,12 +54,6 @@ table td, .table .td {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-wrapper .cbi-input-checkbox {
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.cbi-tabmenu > li {
|
||||
margin-right: 2px !important;
|
||||
}
|
||||
@@ -82,6 +76,76 @@ table td, .table .td {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#select_all_btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* enable flex for small screens*/
|
||||
@media screen and (max-width: 1152px) {
|
||||
.cbi-section-table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes-default-fieldset {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* meticulously control how each component occupies the limited space we have */
|
||||
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
|
||||
flex: 0 0 40px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-remark {
|
||||
flex: 1 1 30%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-ping, #cbi-passwall-nodes .pw-tcping, #cbi-passwall-nodes .pw-urltest {
|
||||
flex: 0 0 50px;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pw-actions {
|
||||
padding-top: 0 !important;
|
||||
border-top-width: 0 !important;
|
||||
flex: 1 1 350px;
|
||||
}
|
||||
|
||||
#select_all_btn {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* shrink actionbar even further for mobile devices */
|
||||
@media screen and (max-width: 500px) {
|
||||
.node-wrapper {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cbi-button {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 1px !important;
|
||||
}
|
||||
|
||||
.pw-actions {
|
||||
padding-left: 5px!important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable-chosen {
|
||||
background-color: rgba(220, 235, 245, 0.4) !important;
|
||||
opacity: 0.7;
|
||||
@@ -114,11 +178,7 @@ table td, .table .td {
|
||||
function cbi_t_switch(section, tab) {
|
||||
if( cbi_t[section] && cbi_t[section][tab] ) {
|
||||
//在切换选项卡之前,先取消当前激活选项卡的全选状态
|
||||
var btn = document.getElementById("select_all_btn");
|
||||
if (btn) {
|
||||
dechecked_all_node(btn);
|
||||
}
|
||||
|
||||
dechecked_all_node();
|
||||
var o = cbi_t[section][tab];
|
||||
var h = document.getElementById('tab.' + section);
|
||||
for( var tid in cbi_t[section] ) {
|
||||
@@ -143,10 +203,7 @@ table td, .table .td {
|
||||
if (typeof(cbi_t_switch) === "function") {
|
||||
var old_switch = cbi_t_switch;
|
||||
cbi_t_switch = function(section, tab) {
|
||||
var btn = document.getElementById("select_all_btn");
|
||||
if (btn) {
|
||||
dechecked_all_node(btn);
|
||||
}
|
||||
dechecked_all_node();
|
||||
return old_switch(section, tab);
|
||||
};
|
||||
}
|
||||
@@ -249,30 +306,59 @@ table td, .table .td {
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
function set_select_all_state(sectionChecked) {
|
||||
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = true;
|
||||
}
|
||||
btn.value = "<%:DeSelect all%>";
|
||||
btn.setAttribute("onclick", "dechecked_all_node(this)");
|
||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].checked = sectionChecked;
|
||||
}
|
||||
if (selectAllChk) {
|
||||
selectAllChk.checked = sectionChecked;
|
||||
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
selectAllChk.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
|
||||
}
|
||||
if (selectAllBtn) {
|
||||
selectAllBtn.value = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
selectAllBtn.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
set_select_all_state(true);
|
||||
}
|
||||
|
||||
function dechecked_all_node(btn) {
|
||||
set_select_all_state(false);
|
||||
}
|
||||
|
||||
function update_select_state() {
|
||||
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = false;
|
||||
}
|
||||
btn.value = "<%:Select all%>";
|
||||
btn.setAttribute("onclick", "checked_all_node(this)");
|
||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (!nodes.length) return;
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
var checkedCount = 0;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].checked) checkedCount++;
|
||||
}
|
||||
var allChecked = checkedCount === nodes.length;
|
||||
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
||||
|
||||
function updateElement(el) {
|
||||
if (!el) return;
|
||||
if ("checked" in el) el.checked = allChecked;
|
||||
if ("title" in el) el.title = title;
|
||||
if ("value" in el) el.value = title;
|
||||
el.setAttribute("onclick", onclickFunc);
|
||||
}
|
||||
|
||||
updateElement(selectAllChk);
|
||||
updateElement(selectAllBtn);
|
||||
}
|
||||
|
||||
function delete_select_nodes() {
|
||||
@@ -622,6 +708,9 @@ table td, .table .td {
|
||||
<fieldset class="cbi-section cbi-tblsection" id="cbi-passwall-nodes-{{group}}-fieldset">
|
||||
<table class="table cbi-section-table" id="cbi-passwall-nodes-{{group}}-table" style="">
|
||||
<tr class="tr cbi-section-table-titles anonymous">
|
||||
<th class="th cbi-section-table-cell" style="width:20px">
|
||||
<input class="cbi-input-checkbox nodes_select_all" type="checkbox" onclick="checked_all_node(this)" title="<%:Select all%>"/>
|
||||
</th>
|
||||
<th class="th cbi-section-table-cell" style="width:40%"><%:Remarks%></th>
|
||||
<th class="th cbi-section-table-cell" style="width:8%">Ping</th>
|
||||
<th class="th cbi-section-table-cell" style="width:8%">TCPing</th>
|
||||
@@ -642,13 +731,15 @@ table td, .table .td {
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.remarks" value="{{remarks_val}}"/>
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.address" value="{{address_val}}"/>
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.port" value="{{port_val}}"/>
|
||||
<td class="td cbi-value-field">{{remarks}}</td>
|
||||
<td class="td cbi-value-field">{{ping}}</td>
|
||||
<td class="td cbi-value-field">{{tcping}}</td>
|
||||
<td class="td cbi-value-field">{{url_test}}</td>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
||||
<td class="td cbi-value-field pw-checkbox">
|
||||
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" onclick="update_select_state()"/>
|
||||
</td>
|
||||
<td class="td cbi-value-field pw-remark">{{remarks}}</td>
|
||||
<td class="td cbi-value-field pw-ping">{{ping}}</td>
|
||||
<td class="td cbi-value-field pw-tcping">{{tcping}}</td>
|
||||
<td class="td cbi-value-field pw-urltest">{{url_test}}</td>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions pw-actions">
|
||||
<div class="node-wrapper">
|
||||
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
|
||||
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
|
||||
|
||||
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
|
||||
PKG_SOURCE_DATE:=2025-11-23
|
||||
PKG_SOURCE_VERSION:=a156d0b1306b09bf676a72e345ca978ef6093d95
|
||||
PKG_MIRROR_HASH:=96b3463e6f983224f92ad56617727b003aad8b3da562c01a4db2d16ae97e406d
|
||||
PKG_SOURCE_DATE:=2025-11-30
|
||||
PKG_SOURCE_VERSION:=ab6f8b2124b02000f5ffcabb904622f17de6a1e5
|
||||
PKG_MIRROR_HASH:=eaa8e57533acabfb2ddd5981ce0553749ef594bfd801283f58e10d5abacbeb9f
|
||||
|
||||
PKG_LICENSE:=BSD 3-Clause
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifndef OUT_LAST_COMMIT_POSITION_H_
|
||||
#define OUT_LAST_COMMIT_POSITION_H_
|
||||
|
||||
#define LAST_COMMIT_POSITION_NUM 2300
|
||||
#define LAST_COMMIT_POSITION "2300 (a156d0b1306b)"
|
||||
#define LAST_COMMIT_POSITION_NUM 2304
|
||||
#define LAST_COMMIT_POSITION "2304 (ab6f8b2124b0)"
|
||||
|
||||
#endif // OUT_LAST_COMMIT_POSITION_H_
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=25.11.27
|
||||
PKG_VERSION:=25.12.1
|
||||
PKG_RELEASE:=1
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js?v=25.11.27"></script>
|
||||
|
||||
<style>
|
||||
table th, .table .th {
|
||||
@@ -54,12 +54,6 @@ table td, .table .td {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-wrapper .cbi-input-checkbox {
|
||||
flex-grow: 0 !important;
|
||||
flex-shrink: 0;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.cbi-tabmenu > li {
|
||||
margin-right: 2px !important;
|
||||
}
|
||||
@@ -82,6 +76,76 @@ table td, .table .td {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#select_all_btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* enable flex for small screens*/
|
||||
@media screen and (max-width: 1152px) {
|
||||
.cbi-section-table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes-default-fieldset {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* meticulously control how each component occupies the limited space we have */
|
||||
#cbi-passwall-nodes .pw-checkbox, #cbi-passwall-nodes th:nth-child(1) {
|
||||
flex: 0 0 40px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-remark {
|
||||
flex: 1 1 30%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#cbi-passwall-nodes .pw-ping, #cbi-passwall-nodes .pw-tcping, #cbi-passwall-nodes .pw-urltest {
|
||||
flex: 0 0 50px;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pw-actions {
|
||||
padding-top: 0 !important;
|
||||
border-top-width: 0 !important;
|
||||
flex: 1 1 350px;
|
||||
}
|
||||
|
||||
#select_all_btn {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* shrink actionbar even further for mobile devices */
|
||||
@media screen and (max-width: 500px) {
|
||||
.node-wrapper {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cbi-button {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 1px !important;
|
||||
}
|
||||
|
||||
.pw-actions {
|
||||
padding-left: 5px!important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable-chosen {
|
||||
background-color: rgba(220, 235, 245, 0.4) !important;
|
||||
opacity: 0.7;
|
||||
@@ -114,11 +178,7 @@ table td, .table .td {
|
||||
function cbi_t_switch(section, tab) {
|
||||
if( cbi_t[section] && cbi_t[section][tab] ) {
|
||||
//在切换选项卡之前,先取消当前激活选项卡的全选状态
|
||||
var btn = document.getElementById("select_all_btn");
|
||||
if (btn) {
|
||||
dechecked_all_node(btn);
|
||||
}
|
||||
|
||||
dechecked_all_node();
|
||||
var o = cbi_t[section][tab];
|
||||
var h = document.getElementById('tab.' + section);
|
||||
for( var tid in cbi_t[section] ) {
|
||||
@@ -143,10 +203,7 @@ table td, .table .td {
|
||||
if (typeof(cbi_t_switch) === "function") {
|
||||
var old_switch = cbi_t_switch;
|
||||
cbi_t_switch = function(section, tab) {
|
||||
var btn = document.getElementById("select_all_btn");
|
||||
if (btn) {
|
||||
dechecked_all_node(btn);
|
||||
}
|
||||
dechecked_all_node();
|
||||
return old_switch(section, tab);
|
||||
};
|
||||
}
|
||||
@@ -249,30 +306,59 @@ table td, .table .td {
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
function set_select_all_state(sectionChecked) {
|
||||
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = true;
|
||||
}
|
||||
btn.value = "<%:DeSelect all%>";
|
||||
btn.setAttribute("onclick", "dechecked_all_node(this)");
|
||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].checked = sectionChecked;
|
||||
}
|
||||
if (selectAllChk) {
|
||||
selectAllChk.checked = sectionChecked;
|
||||
selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
selectAllChk.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
|
||||
}
|
||||
if (selectAllBtn) {
|
||||
selectAllBtn.value = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
selectAllBtn.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
set_select_all_state(true);
|
||||
}
|
||||
|
||||
function dechecked_all_node(btn) {
|
||||
set_select_all_state(false);
|
||||
}
|
||||
|
||||
function update_select_state() {
|
||||
var visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
var doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = false;
|
||||
}
|
||||
btn.value = "<%:Select all%>";
|
||||
btn.setAttribute("onclick", "checked_all_node(this)");
|
||||
var nodes = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (!nodes.length) return;
|
||||
var selectAllChk = visibleContainer.querySelector(".nodes_select_all");
|
||||
var selectAllBtn = document.getElementById("select_all_btn");
|
||||
var checkedCount = 0;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].checked) checkedCount++;
|
||||
}
|
||||
var allChecked = checkedCount === nodes.length;
|
||||
var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>";
|
||||
var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)";
|
||||
|
||||
function updateElement(el) {
|
||||
if (!el) return;
|
||||
if ("checked" in el) el.checked = allChecked;
|
||||
if ("title" in el) el.title = title;
|
||||
if ("value" in el) el.value = title;
|
||||
el.setAttribute("onclick", onclickFunc);
|
||||
}
|
||||
|
||||
updateElement(selectAllChk);
|
||||
updateElement(selectAllBtn);
|
||||
}
|
||||
|
||||
function delete_select_nodes() {
|
||||
@@ -622,6 +708,9 @@ table td, .table .td {
|
||||
<fieldset class="cbi-section cbi-tblsection" id="cbi-passwall-nodes-{{group}}-fieldset">
|
||||
<table class="table cbi-section-table" id="cbi-passwall-nodes-{{group}}-table" style="">
|
||||
<tr class="tr cbi-section-table-titles anonymous">
|
||||
<th class="th cbi-section-table-cell" style="width:20px">
|
||||
<input class="cbi-input-checkbox nodes_select_all" type="checkbox" onclick="checked_all_node(this)" title="<%:Select all%>"/>
|
||||
</th>
|
||||
<th class="th cbi-section-table-cell" style="width:40%"><%:Remarks%></th>
|
||||
<th class="th cbi-section-table-cell" style="width:8%">Ping</th>
|
||||
<th class="th cbi-section-table-cell" style="width:8%">TCPing</th>
|
||||
@@ -642,13 +731,15 @@ table td, .table .td {
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.remarks" value="{{remarks_val}}"/>
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.address" value="{{address_val}}"/>
|
||||
<input class="hidden" id="cbid.passwall.{{id}}.port" value="{{port_val}}"/>
|
||||
<td class="td cbi-value-field">{{remarks}}</td>
|
||||
<td class="td cbi-value-field">{{ping}}</td>
|
||||
<td class="td cbi-value-field">{{tcping}}</td>
|
||||
<td class="td cbi-value-field">{{url_test}}</td>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
||||
<td class="td cbi-value-field pw-checkbox">
|
||||
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" onclick="update_select_state()"/>
|
||||
</td>
|
||||
<td class="td cbi-value-field pw-remark">{{remarks}}</td>
|
||||
<td class="td cbi-value-field pw-ping">{{ping}}</td>
|
||||
<td class="td cbi-value-field pw-tcping">{{tcping}}</td>
|
||||
<td class="td cbi-value-field pw-urltest">{{url_test}}</td>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions pw-actions">
|
||||
<div class="node-wrapper">
|
||||
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
|
||||
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
|
||||
|
||||
@@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=2445b44d9ae3ab9a867c9d1e0e244646c4c378622e14b9afaf3658ecf46a40b9
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20251129160841
|
||||
GEOSITE_VER:=20251130074021
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=eabd8f8d95cfc4d2149b60a94cc5163564e77a65ccbe6baf90cd91507a8c806b
|
||||
HASH:=7e4c0877aabed22425aa651859e2f705f516a154ece96f30cccef71c1dc4a500
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202511240043
|
||||
|
||||
@@ -350,10 +350,11 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header
|
||||
return w.Writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
isComplete := IsCompleteRecord(mb)
|
||||
mb = ReshapeMultiBuffer(w.ctx, mb)
|
||||
longPadding := w.trafficState.IsTLS
|
||||
for i, b := range mb {
|
||||
if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) {
|
||||
if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete {
|
||||
if w.trafficState.EnableXtls {
|
||||
*switchToDirectCopy = true
|
||||
}
|
||||
@@ -386,6 +387,71 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
return w.Writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
|
||||
// IsCompleteRecord Is complete tls data record
|
||||
func IsCompleteRecord(buffer buf.MultiBuffer) bool {
|
||||
mb2 := make(buf.MultiBuffer, 0, len(buffer))
|
||||
for _, buffer1 := range buffer {
|
||||
buffer2 := buf.New()
|
||||
buffer2.Write(buffer1.Bytes())
|
||||
mb2 = append(mb2, buffer2)
|
||||
}
|
||||
isComplete := true
|
||||
var headerLen int32 = 5
|
||||
var recordLen int32
|
||||
for _, buffer2 := range mb2 {
|
||||
for buffer2.Len() > 0 {
|
||||
if headerLen > 0 {
|
||||
data, _ := buffer2.ReadByte()
|
||||
switch headerLen {
|
||||
case 5:
|
||||
if data != 0x17 {
|
||||
isComplete = false
|
||||
break
|
||||
}
|
||||
case 4:
|
||||
if data != 0x03 {
|
||||
isComplete = false
|
||||
break
|
||||
}
|
||||
case 3:
|
||||
if data != 0x03 {
|
||||
isComplete = false
|
||||
break
|
||||
}
|
||||
case 2:
|
||||
recordLen = int32(data) << 8
|
||||
case 1:
|
||||
recordLen = recordLen | int32(data)
|
||||
}
|
||||
headerLen--
|
||||
} else if recordLen > 0 {
|
||||
var len = recordLen
|
||||
if buffer2.Len() < recordLen{
|
||||
len = buffer2.Len()
|
||||
}
|
||||
buffer2.Advance(len)
|
||||
recordLen -= len
|
||||
if recordLen == 0 {
|
||||
headerLen = 5
|
||||
}
|
||||
} else {
|
||||
isComplete = false
|
||||
}
|
||||
}
|
||||
if !isComplete {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, buffer2 := range mb2 {
|
||||
buffer2.Release()
|
||||
buffer2 = nil
|
||||
}
|
||||
if headerLen == 5 && recordLen == 0 && isComplete {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes)
|
||||
func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer {
|
||||
needReshape := 0
|
||||
|
||||
18
yt-dlp/.github/workflows/build.yml
vendored
18
yt-dlp/.github/workflows/build.yml
vendored
@@ -422,23 +422,23 @@ jobs:
|
||||
runner: windows-2025
|
||||
python_version: '3.10'
|
||||
platform_tag: win_amd64
|
||||
pyi_version: '6.16.0'
|
||||
pyi_tag: '2025.09.13.221251'
|
||||
pyi_hash: b6496c7630c3afe66900cfa824e8234a8c2e2c81704bd7facd79586abc76c0e5
|
||||
pyi_version: '6.17.0'
|
||||
pyi_tag: '2025.11.29.054325'
|
||||
pyi_hash: e28cc13e4ad0cc74330d832202806d0c1976e9165da6047309348ca663c0ed3d
|
||||
- arch: 'x86'
|
||||
runner: windows-2025
|
||||
python_version: '3.10'
|
||||
platform_tag: win32
|
||||
pyi_version: '6.16.0'
|
||||
pyi_tag: '2025.09.13.221251'
|
||||
pyi_hash: 2d881843580efdc54f3523507fc6d9c5b6051ee49c743a6d9b7003ac5758c226
|
||||
pyi_version: '6.17.0'
|
||||
pyi_tag: '2025.11.29.054325'
|
||||
pyi_hash: c00f600c17de3bdd589f043f60ab64fc34fcba6dd902ad973af9c8afc74f80d1
|
||||
- arch: 'arm64'
|
||||
runner: windows-11-arm
|
||||
python_version: '3.13' # arm64 only has Python >= 3.11 available
|
||||
platform_tag: win_arm64
|
||||
pyi_version: '6.16.0'
|
||||
pyi_tag: '2025.09.13.221251'
|
||||
pyi_hash: 4250c9085e34a95c898f3ee2f764914fc36ec59f0d97c28e6a75fcf21f7b144f
|
||||
pyi_version: '6.17.0'
|
||||
pyi_tag: '2025.11.29.054325'
|
||||
pyi_hash: a2033b18b4f7bc6108b5fd76a92c6c1de0a12ec4fe98a23396a9f978cb4b7d7b
|
||||
env:
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
ORIGIN: ${{ needs.process.outputs.origin }}
|
||||
|
||||
@@ -69,7 +69,7 @@ build = [
|
||||
"build",
|
||||
"hatchling>=1.27.0",
|
||||
"pip",
|
||||
"setuptools>=71.0.2,<81", # See https://github.com/pyinstaller/pyinstaller/issues/9149
|
||||
"setuptools>=71.0.2",
|
||||
"wheel",
|
||||
]
|
||||
dev = [
|
||||
@@ -86,7 +86,7 @@ test = [
|
||||
"pytest-rerunfailures~=14.0",
|
||||
]
|
||||
pyinstaller = [
|
||||
"pyinstaller>=6.13.0", # Windows temp cleanup fixed in 6.13.0
|
||||
"pyinstaller>=6.17.0", # 6.17.0+ needed for compat with setuptools 81+
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
Reference in New Issue
Block a user