Compare commits

...

42 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
a3b48fc01c chore: bump version 2024-07-26 23:35:59 +08:00
lollipopkit🏳️‍⚧️
8be94aa09c feat: use $EDITOR to edit files (#496)
Fixes #489
2024-07-26 23:32:57 +08:00
lollipopkit🏳️‍⚧️
5db1253ab8 fix: termux compatibility (#495) 2024-07-26 22:31:17 +08:00
lollipopkit🏳️‍⚧️
ceedd86310 rm: pkg (#494)
Fixes #470
2024-07-26 21:31:45 +08:00
lollipopkit🏳️‍⚧️
6a0254623f opt.: json input experience 2024-07-26 20:22:30 +08:00
lollipopkit🏳️‍⚧️
1c6ec56032 Create FUNDING.yml 2024-07-25 10:42:18 +08:00
lollipopkit🏳️‍⚧️
287869ed45 opt.: simplify settings page (#488) 2024-07-24 00:30:17 +08:00
lollipopkit🏳️‍⚧️
e4dbc3ba12 feat: set envs in term (#485) 2024-07-23 21:34:34 +08:00
lollipopkit🏳️‍⚧️
426e5689f8 fix: uploaded file's path on windows (#484) 2024-07-23 19:59:58 +08:00
lollipopkit🏳️‍⚧️
afda5fd4a4 fix: bio auth (#482) 2024-07-23 19:26:03 +08:00
lollipopkit🏳️‍⚧️
0a21b2820c fix: letterCacheTip translation 2024-07-23 12:14:31 +08:00
lollipopkit🏳️‍⚧️
87b3b76b0b feat: display cpu model (#477) 2024-07-23 12:03:10 +08:00
lollipopkit🏳️‍⚧️
41ec46f1d3 opt.: show loading dialog 2024-07-22 23:33:21 +08:00
lollipopkit🏳️‍⚧️
7a359588db opt.: input field suggestion 2024-07-22 22:03:56 +08:00
lollipopkit🏳️‍⚧️
255abe8b11 rollback: write script to /dev/shm (#474) 2024-07-21 17:58:14 +08:00
lollipopkit🏳️‍⚧️
b0936c5e6e fix: termux compatibility (#472) 2024-07-20 20:35:30 +08:00
lollipopkit🏳️‍⚧️
2907ac74d4 chore: bump version 2024-07-18 23:44:01 +08:00
lollipopkit🏳️‍⚧️
ea678f37b0 chore: bump version 2024-07-18 23:15:40 +08:00
lollipopkit🏳️‍⚧️
076082c945 feat: sftp perm setting & path copy (#467) 2024-07-18 21:40:40 +08:00
lollipopkit🏳️‍⚧️
5ee98f90e8 fix: update changelog (#466) 2024-07-18 20:53:22 +08:00
lollipopkit🏳️‍⚧️
c988dd88d7 fix: linux duplicated title bar (#462)
Fixes #459
2024-07-15 17:38:30 +08:00
lollipopkit🏳️‍⚧️
f7d6c461dc fix: version display (#458)
Fixes #457
2024-07-10 15:12:05 +08:00
lollipopkit🏳️‍⚧️
14771ae946 chore: README 2024-07-09 14:36:04 +08:00
lollipopkit🏳️‍⚧️
7e9086b20e fix: chmod perm 2024-07-09 14:02:23 +08:00
Noo6
4b3c4870ba fix: desktop window appbar (#450) 2024-07-05 12:42:38 +08:00
Noo6
43cebd0c04 fix: window blink on startup (#447) 2024-07-04 12:23:20 +08:00
Noo6
5ce13109b0 fix: ssh card tap area (#448) 2024-07-04 12:22:19 +08:00
lollipopkit🏳️‍⚧️
6e428c91d1 feat: disable letter cache (#446)
Fixes #445
2024-07-03 19:55:33 +08:00
lollipopkit🏳️‍⚧️
4430045550 feat: write script into /dev/shm (#444)
Fixes #443
2024-07-03 19:14:27 +08:00
lollipopkit🏳️‍⚧️
282cb06091 fix: picker dialog (#442)
Fixes #440
2024-07-03 18:19:09 +08:00
lollipopkit🏳️‍⚧️
772c2743b5 fix: no appBar in server detail page (#441)
Fixes #435
2024-07-03 16:42:52 +08:00
lollipopkit🏳️‍⚧️
90199b89a5 chore: README 2024-07-03 00:52:28 +08:00
Integral
e1d2e3f3e5 l10n: fix remaining translations (#439)
* l10n: Url -> URL

* l10n: Logo Address -> Logo URL

* l10n: fix ttl translation

* l10n: fix all zh-tw translations

* l10n: fix all zh-cn translations

* l10n: fix all en translations
2024-07-02 15:02:49 +00:00
Integral
3f9fe1f2c6 l10n: alterUrl -> fallbackSshDest (#437) 2024-07-02 13:15:19 +00:00
Integral
c79bbc5756 l10n: fix two acronyms (MAC, URL) (#436)
* l10n: Mac Address -> MAC Address

* l10n: PVE addr -> URL
2024-07-01 14:56:38 +00:00
Integral
63e1bec2b9 l10n: fix translation of net/network (#433) 2024-07-01 07:01:51 +00:00
Integral
d26b7c6f75 l10n: add noLineChartForCpu field (#431) 2024-07-01 05:24:31 +00:00
Integral
da8dc4fa54 docs(README): add f-droid & reorganize other installation methods (#429)
* docs(README): add f-droid & reorganize other installation methods

* docs(README): update Chinese README from English
2024-06-30 11:51:36 +00:00
Integral
413b81c47f l10n: fix Japanese translations (#425) 2024-06-25 16:00:42 +00:00
Integral
9ef419e3df l10n: fix English translations (#424) 2024-06-25 16:00:26 +00:00
Integral
39893912d9 Merge pull request #423 from Integral-Tech/fix-fdroid-typo
chore: fix typo of "F-Droid"
2024-06-25 14:04:16 +00:00
Integral
49456ca6c3 chore: fix typo of "F-Droid" 2024-06-25 22:00:46 +08:00
73 changed files with 1760 additions and 1149 deletions

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: lollipopkit
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: ['https://afdian.com/a/lollipopkit'] # Replace with up to 4 custom sponsorship URLs

View File

@@ -15,8 +15,6 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Install Flutter - name: Install Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
@@ -54,8 +52,6 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Install Flutter - name: Install Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
- name: Build - name: Build

View File

@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "bae5e49bc2a867403c43b2aae2de8f8c33b037e4" revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,26 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: android - platform: android
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: ios - platform: ios
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: linux - platform: linux
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: macos - platform: macos
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: web - platform: web
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: windows - platform: windows
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
# User provided section # User provided section

4
.vscode/launch.json vendored
View File

@@ -8,6 +8,10 @@
"name": "debug", "name": "debug",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"env": {
// Comment this line to use the default display
"DISPLAY": ":1"
}
// "args": [ // "args": [
// "-v" // "-v"
// ] // ]

View File

@@ -14,17 +14,22 @@ Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartss
</p> </p>
## ⬇️ Download ## 📥 Install
🎉 **The `Android / Linux / Windows` version are now built via GitHub Actions**
[iOS & macOS](https://apps.apple.com/app/id1586449703) / [Android & Linux & Windows](https://github.com/lollipopkit/flutter_server_box/releases) Platform | From
--- | ---
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
- All deprecated versions before `v930` can be found in [here](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid). **Please only download pkgs from the source that you trust!**
- To prevent injection attacks and etc., please don't download from untrusted sources. eg: Gitee release is not related to this project. - `AppStore` & `CDN` packages are built by myself
- Github releases are built by Github Actions
- Other sources are built by themselves
## 🔖 Feature ## 🔖 Feature
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Pkg & Process`... - `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`...
- Platform specific: `Bio auth``Msg push``Home widget``watchOS App`... - Platform specific: `Bio auth``Msg push``Home widget``watchOS App`...
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic); Español, Русский язык, Português, 日本語 (Generated by GPT) - English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic); Español, Русский язык, Português, 日本語 (Generated by GPT)
@@ -32,16 +37,14 @@ Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartss
## 🏙️ ScreenShots ## 🏙️ ScreenShots
<table> <table>
<tr> <tr>
<td><img width="277px" src="imgs/server.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/1.png"></td>
<td><img width="277px" src="imgs/detail.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/2.png"></td>
<td><img width="277px" src="imgs/sftp.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/3.png"></td>
</tr> </tr>
</table>
<table>
<tr> <tr>
<td><img width="277px" src="imgs/editor.png"> </td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/4.png"> </td>
<td><img width="277px" src="imgs/ssh.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/5.png"></td>
<td><img width="277px" src="imgs/docker.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/6.png"></td>
</tr> </tr>
</table> </table>
@@ -59,8 +62,16 @@ After you read the above, you can open an [issue](https://github.com/lollipopkit
## 🧱 Contribution ## 🧱 Contribution
- Any positive contribution is welcome. Any positive contribution is welcome.
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.
### Development
1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment.
2. Clone this repo, run `flutter run` to start the app.
3. Run `dart run fl_build -p PLATFORM` to build the app.
### Translation
- [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog.
- We need your help! Just feel free to open a PR.
## 💡 My other apps ## 💡 My other apps

View File

@@ -13,18 +13,22 @@
特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a> 特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>
</p> </p>
## 📥 安装
## ⬇️ Download 平台 | 下载
🎉 **现在 `Android / Linux / Windows` 版本使用 GitHub Actions 构建** --- | ---
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
[iOS & macOS](https://apps.apple.com/app/id1586449703) / [Android & Linux & Windows](https://github.com/lollipopkit/flutter_server_box/releases) **请不要从不受信任的来源下载!**
- `AppStore` & `CDN` 的包由我构建
- 所有 `v930` 之前的版本可以在 [这里](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) 找到。 - Github 的包由 Github Actions 构建
- 为了防止注入攻击等请不要从不受信任的来源下载。例如Gitee 的发行包与该项目无关。 - 其他来源由其所有者构建
## 🔖 特点 ## 🔖 特点
- `状态图表`CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理... - `状态图表`CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程` 管理...
- 特殊支持:`生物认证``推送``桌面小部件``watchOS App``跟随系统颜色`... - 特殊支持:`生物认证``推送``桌面小部件``watchOS App``跟随系统颜色`...
- 本地化 - 本地化
- English, 简体中文 - English, 简体中文
@@ -35,16 +39,14 @@
## 🏙️ 截屏 ## 🏙️ 截屏
<table> <table>
<tr> <tr>
<td><img width="277px" src="imgs/server.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/1.png"></td>
<td><img width="277px" src="imgs/detail.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/2.png"></td>
<td><img width="277px" src="imgs/sftp.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/3.png"></td>
</tr> </tr>
</table>
<table>
<tr> <tr>
<td><img width="277px" src="imgs/editor.png"> </td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/4.png"> </td>
<td><img width="277px" src="imgs/ssh.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/5.png"></td>
<td><img width="277px" src="imgs/docker.png"></td> <td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/6.png"></td>
</tr> </tr>
</table> </table>
@@ -64,9 +66,15 @@
## 🧱 贡献 ## 🧱 贡献
- 任何正面的贡献都欢迎。 任何正面的贡献都欢迎。
- [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
### 开发
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
2. 克隆这个仓库, 运行 `flutter run` 启动应用
3. 运行 `dart run fl_build -p PLATFORM` 构建应用
### 翻译
[指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
## 💡 我的其它 Apps ## 💡 我的其它 Apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。 - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -690,7 +690,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -700,7 +700,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -826,7 +826,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -836,7 +836,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -854,7 +854,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -864,7 +864,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -885,7 +885,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -898,7 +898,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -924,7 +924,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -937,7 +937,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -960,7 +960,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -973,7 +973,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -996,7 +996,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1008,7 +1008,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1037,7 +1037,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1049,7 +1049,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;
@@ -1075,7 +1075,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1087,7 +1087,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;

View File

@@ -3,6 +3,7 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart'; import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:locale_names/locale_names.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/rebuild.dart'; import 'package:server_box/data/res/rebuild.dart';
@@ -23,7 +24,18 @@ class MyApp extends StatelessWidget {
builder: (context, _) { builder: (context, _) {
if (!Stores.setting.useSystemPrimaryColor.fetch()) { if (!Stores.setting.useSystemPrimaryColor.fetch()) {
UIs.colorSeed = Color(Stores.setting.primaryColor.fetch()); UIs.colorSeed = Color(Stores.setting.primaryColor.fetch());
return _buildApp(context); return _buildApp(
context,
light: ThemeData(
useMaterial3: true,
colorSchemeSeed: UIs.colorSeed,
),
dark: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: UIs.colorSeed,
),
);
} }
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (light, dark) { builder: (light, dark) {
@@ -36,10 +48,10 @@ class MyApp extends StatelessWidget {
brightness: Brightness.dark, brightness: Brightness.dark,
colorScheme: dark, colorScheme: dark,
); );
if (context.isDark && light != null) { if (context.isDark && dark != null) {
UIs.primaryColor = light.primary;
} else if (!context.isDark && dark != null) {
UIs.primaryColor = dark.primary; UIs.primaryColor = dark.primary;
} else if (!context.isDark && light != null) {
UIs.primaryColor = light.primary;
} }
return _buildApp(context, light: lightTheme, dark: darkTheme); return _buildApp(context, light: lightTheme, dark: darkTheme);
}, },
@@ -48,7 +60,8 @@ class MyApp extends StatelessWidget {
); );
} }
Widget _buildApp(BuildContext ctx, {ThemeData? light, ThemeData? dark}) { Widget _buildApp(BuildContext ctx,
{required ThemeData light, required ThemeData dark}) {
final tMode = Stores.setting.themeMode.fetch(); final tMode = Stores.setting.themeMode.fetch();
// Issue #57 // Issue #57
final themeMode = switch (tMode) { final themeMode = switch (tMode) {
@@ -58,16 +71,6 @@ class MyApp extends StatelessWidget {
}; };
final locale = Stores.setting.locale.fetch().toLocale; final locale = Stores.setting.locale.fetch().toLocale;
light ??= ThemeData(
useMaterial3: true,
colorSchemeSeed: UIs.colorSeed,
);
dark ??= ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: UIs.colorSeed,
);
return MaterialApp( return MaterialApp(
locale: locale, locale: locale,
localizationsDelegates: const [ localizationsDelegates: const [
@@ -76,41 +79,27 @@ class MyApp extends StatelessWidget {
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
localeListResolutionCallback: LocaleUtil.resolve, localeListResolutionCallback: LocaleUtil.resolve,
navigatorObservers: [AppRouteObserver.instance],
title: BuildData.name, title: BuildData.name,
themeMode: themeMode, themeMode: themeMode,
theme: light, theme: light.fixWindowsFont,
darkTheme: tMode < 3 ? dark : dark.toAmoled, darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
home: _buildAppContent(ctx), home: Builder(
builder: (context) {
context.setLibL10n();
final appL10n = AppLocalizations.of(context);
if (appL10n != null) l10n = appL10n;
final intros = _IntroPage.builders;
if (intros.isNotEmpty) {
return _IntroPage(intros);
}
return const HomePage();
},
),
); );
} }
Widget _buildAppContent(BuildContext ctx) {
//if (Pros.app.isWearOS) return const WearHome();
return const _AppContent(
intro: _IntroPage(),
child: HomePage(),
);
}
}
/// It's used for init settings related to [BuildContext]
final class _AppContent extends StatelessWidget {
final Widget child;
final Widget intro;
const _AppContent({required this.child, required this.intro});
@override
Widget build(BuildContext context) {
context.setLibL10n();
final appL10n = AppLocalizations.of(context);
if (appL10n != null) l10n = appL10n;
final showIntro = Stores.setting.showIntro.fetch();
if (showIntro) return intro;
return child;
}
} }
void _setup(BuildContext context) async { void _setup(BuildContext context) async {

View File

@@ -1,4 +1,5 @@
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:server_box/view/widget/unix_perm.dart';
extension SftpFileX on SftpFileMode { extension SftpFileX on SftpFileMode {
String get str { String get str {
@@ -8,6 +9,26 @@ extension SftpFileX on SftpFileMode {
return '$user$group$other'; return '$user$group$other';
} }
UnixPerm toUnixPerm() {
return UnixPerm(
user: RWX(
r: userRead,
w: userWrite,
x: userExecute,
),
group: RWX(
r: groupRead,
w: groupWrite,
x: groupExecute,
),
other: RWX(
r: otherRead,
w: otherWrite,
x: otherExecute,
),
);
}
} }
String _getRoleMode(bool r, bool w, bool x) { String _getRoleMode(bool r, bool w, bool x) {

View File

@@ -250,6 +250,9 @@ class AppRoutes {
} }
static AppRoutes kvEditor({Key? key, required Map<String, String> data}) { static AppRoutes kvEditor({Key? key, required Map<String, String> data}) {
return AppRoutes(KvEditor(key: key, data: data), 'kv_editor'); return AppRoutes(
KvEditor(key: key, args: KvEditorArgs(data: data)),
'kv_editor',
);
} }
} }

View File

@@ -170,7 +170,7 @@ class Backup {
} }
Pros.reload(); Pros.reload();
RNodes.app.build(); RNodes.app.notify();
_logger.info('Restore success'); _logger.info('Restore success');
} }

View File

@@ -15,8 +15,8 @@ enum ServerFuncBtn {
container, container,
@HiveField(3) @HiveField(3)
process, process,
@HiveField(4) //@HiveField(4)
pkg, //pkg,
@HiveField(5) @HiveField(5)
snippet, snippet,
@HiveField(6) @HiveField(6)
@@ -30,14 +30,14 @@ enum ServerFuncBtn {
sftp, sftp,
container, container,
process, process,
pkg, //pkg,
snippet, snippet,
].map((e) => e.index).toList(); ].map((e) => e.index).toList();
IconData get icon => switch (this) { IconData get icon => switch (this) {
sftp => Icons.insert_drive_file, sftp => Icons.insert_drive_file,
snippet => Icons.code, snippet => Icons.code,
pkg => Icons.system_security_update, //pkg => Icons.system_security_update,
container => FontAwesome.docker_brand, container => FontAwesome.docker_brand,
process => Icons.list_alt_outlined, process => Icons.list_alt_outlined,
terminal => Icons.terminal, terminal => Icons.terminal,
@@ -47,7 +47,7 @@ enum ServerFuncBtn {
String get toStr => switch (this) { String get toStr => switch (this) {
sftp => 'SFTP', sftp => 'SFTP',
snippet => l10n.snippet, snippet => l10n.snippet,
pkg => l10n.pkg, //pkg => l10n.pkg,
container => l10n.container, container => l10n.container,
process => l10n.process, process => l10n.process,
terminal => l10n.terminal, terminal => l10n.terminal,

View File

@@ -21,8 +21,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
return ServerFuncBtn.container; return ServerFuncBtn.container;
case 3: case 3:
return ServerFuncBtn.process; return ServerFuncBtn.process;
case 4:
return ServerFuncBtn.pkg;
case 5: case 5:
return ServerFuncBtn.snippet; return ServerFuncBtn.snippet;
case 6: case 6:
@@ -47,9 +45,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
case ServerFuncBtn.process: case ServerFuncBtn.process:
writer.writeByte(3); writer.writeByte(3);
break; break;
case ServerFuncBtn.pkg:
writer.writeByte(4);
break;
case ServerFuncBtn.snippet: case ServerFuncBtn.snippet:
writer.writeByte(5); writer.writeByte(5);
break; break;

View File

@@ -12,52 +12,32 @@ enum ShellFunc {
suspend, suspend,
; ;
static const _homeVar = '\$HOME';
static const seperator = 'SrvBoxSep'; static const seperator = 'SrvBoxSep';
/// The suffix `\t` is for formatting /// The suffix `\t` is for formatting
static const cmdDivider = '\necho $seperator\n\t'; static const cmdDivider = '\necho $seperator\n\t';
static const _srvBoxDir = '.config/server_box';
static const scriptFile = 'mobile_v${BuildData.script}.sh';
/// Issue #159 /// srvboxm -> ServerBox Mobile
/// static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
/// Use script commit count as version of shell script. static const scriptDir = '~/.config/server_box';
/// static const scriptPath = '$scriptDir/$scriptFile';
/// So different version of app can run at the same time.
///
/// **Can't** use it in SFTP, because SFTP can't recognize `$HOME`
static String getShellPath(String home) => '$home/$_srvBoxDir/$scriptFile';
static const srvBoxDir = '$_homeVar/$_srvBoxDir'; static const String installShellCmd = """
static const _installShellPath = '$_homeVar/$_srvBoxDir/$scriptFile'; mkdir -p $scriptDir
cat > $scriptPath
// Issue #299, chmod ~/.config to avoid permission issue chmod 744 $scriptPath
static const installShellCmd = """
chmod +x ~/.config &> /dev/null
mkdir -p $_homeVar/$_srvBoxDir
cat > $_installShellPath
chmod +x $_installShellPath
"""; """;
String get flag { String get flag => switch (this) {
switch (this) { ShellFunc.process => 'p',
case ShellFunc.status: ShellFunc.shutdown => 'sd',
return 's'; ShellFunc.reboot => 'r',
// case ShellFunc.docker: ShellFunc.suspend => 'sp',
// return 'd'; ShellFunc.status => 's',
case ShellFunc.process: // ShellFunc.docker=> 'd',
return 'p'; };
case ShellFunc.shutdown:
return 'sd';
case ShellFunc.reboot:
return 'r';
case ShellFunc.suspend:
return 'sp';
}
}
String get exec => 'sh $_installShellPath -$flag'; String get exec => 'sh $scriptPath -$flag';
String get name { String get name {
switch (this) { switch (this) {
@@ -213,6 +193,7 @@ enum StatusCmdType {
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'), 'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
nvidia._('nvidia-smi -q -x'), nvidia._('nvidia-smi -q -x'),
sensors._('sensors'), sensors._('sensors'),
cpuBrand._('cat /proc/cpuinfo | grep "model name"'),
; ;
final String cmd; final String cmd;
@@ -231,6 +212,7 @@ enum BSDStatusCmdType {
mem._('top -l 1 | grep PhysMem'), mem._('top -l 1 | grep PhysMem'),
//temp, //temp,
host._('hostname'), host._('hostname'),
cpuBrand._('sysctl -n machdep.cpu.brand_string'),
; ;
final String cmd; final String cmd;

View File

@@ -1,14 +1,16 @@
import 'dart:collection';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/time_seq.dart'; import 'package:server_box/data/model/server/time_seq.dart';
import 'package:server_box/data/res/status.dart'; import 'package:server_box/data/res/status.dart';
/// Capacity of the FIFO queue
const _kCap = 30; const _kCap = 30;
class Cpus extends TimeSeq<List<SingleCpuCore>> { class Cpus extends TimeSeq<List<SingleCpuCore>> {
Cpus(super.init1, super.init2); Cpus(super.init1, super.init2);
final Map<String, int> brand = {};
@override @override
void onUpdate() { void onUpdate() {
_coresCount = now.length; _coresCount = now.length;
@@ -23,10 +25,16 @@ class Cpus extends TimeSeq<List<SingleCpuCore>> {
double usedPercent({int coreIdx = 0}) { double usedPercent({int coreIdx = 0}) {
if (now.length != pre.length) return 0; if (now.length != pre.length) return 0;
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle; if (now.isEmpty) return 0;
final totalDelta = now[coreIdx].total - pre[coreIdx].total; try {
final used = idleDelta / totalDelta; final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
return used.isNaN ? 0 : 100 - used * 100; final totalDelta = now[coreIdx].total - pre[coreIdx].total;
final used = idleDelta / totalDelta;
return used.isNaN ? 0 : 100 - used * 100;
} catch (e, s) {
Loggers.app.warning('Cpus.usedPercent()', e, s);
return 0;
}
} }
int _coresCount = 0; int _coresCount = 0;
@@ -175,6 +183,22 @@ class SingleCpuCore extends TimeSeqIface<SingleCpuCore> {
} }
} }
final class CpuBrand {
static Map<String, int> parse(String raw) {
final lines = raw.split('\n');
// {brand: count}
final brands = <String, int>{};
for (var line in lines) {
if (line.contains('model name')) {
final model = line.split(':').last.trim();
final count = brands[model] ?? 0;
brands[model] = count + 1;
}
}
return brands;
}
}
final _bsdCpuPercentReg = RegExp(r'(\d+\.\d+)%'); final _bsdCpuPercentReg = RegExp(r'(\d+\.\d+)%');
/// TODO: Change this implementation to parse cpu status on BSD system /// TODO: Change this implementation to parse cpu status on BSD system

View File

@@ -42,6 +42,10 @@ class ServerPrivateInfo {
@HiveField(11) @HiveField(11)
final WakeOnLanCfg? wolCfg; final WakeOnLanCfg? wolCfg;
/// It only applies to SSH terminal.
@HiveField(12)
final Map<String, String>? envs;
final String id; final String id;
const ServerPrivateInfo({ const ServerPrivateInfo({
@@ -57,6 +61,7 @@ class ServerPrivateInfo {
this.jumpId, this.jumpId,
this.custom, this.custom,
this.wolCfg, this.wolCfg,
this.envs,
}) : id = '$user@$ip:$port'; }) : id = '$user@$ip:$port';
static ServerPrivateInfo fromJson(Map<String, dynamic> json) { static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
@@ -76,6 +81,15 @@ class ServerPrivateInfo {
final wolCfg = json["wolCfg"] == null final wolCfg = json["wolCfg"] == null
? null ? null
: WakeOnLanCfg.fromJson(json["wolCfg"].cast<String, dynamic>()); : WakeOnLanCfg.fromJson(json["wolCfg"].cast<String, dynamic>());
final envs_ = json["envs"] as Map<String, dynamic>?;
final envs = <String, String>{};
if (envs_ != null) {
envs_.forEach((key, value) {
if (value is String) {
envs[key] = value;
}
});
}
return ServerPrivateInfo( return ServerPrivateInfo(
name: name, name: name,
@@ -90,6 +104,7 @@ class ServerPrivateInfo {
jumpId: jumpId, jumpId: jumpId,
custom: custom, custom: custom,
wolCfg: wolCfg, wolCfg: wolCfg,
envs: envs.isEmpty ? null : envs,
); );
} }
@@ -123,6 +138,9 @@ class ServerPrivateInfo {
if (wolCfg != null) { if (wolCfg != null) {
data["wolCfg"] = wolCfg?.toJson(); data["wolCfg"] = wolCfg?.toJson();
} }
if (envs != null) {
data["envs"] = envs;
}
return data; return data;
} }

View File

@@ -29,13 +29,14 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
jumpId: fields[9] as String?, jumpId: fields[9] as String?,
custom: fields[10] as ServerCustom?, custom: fields[10] as ServerCustom?,
wolCfg: fields[11] as WakeOnLanCfg?, wolCfg: fields[11] as WakeOnLanCfg?,
envs: (fields[12] as Map?)?.cast<String, String>(),
); );
} }
@override @override
void write(BinaryWriter writer, ServerPrivateInfo obj) { void write(BinaryWriter writer, ServerPrivateInfo obj) {
writer writer
..writeByte(12) ..writeByte(13)
..writeByte(0) ..writeByte(0)
..write(obj.name) ..write(obj.name)
..writeByte(1) ..writeByte(1)
@@ -59,7 +60,9 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
..writeByte(10) ..writeByte(10)
..write(obj.custom) ..write(obj.custom)
..writeByte(11) ..writeByte(11)
..write(obj.wolCfg); ..write(obj.wolCfg)
..writeByte(12)
..write(obj.envs);
} }
@override @override

View File

@@ -71,6 +71,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
try { try {
final cpus = SingleCpuCore.parse(StatusCmdType.cpu.find(segments)); final cpus = SingleCpuCore.parse(StatusCmdType.cpu.find(segments));
req.ss.cpu.update(cpus); req.ss.cpu.update(cpus);
final brand = CpuBrand.parse(StatusCmdType.cpuBrand.find(segments));
req.ss.cpu.brand.clear();
req.ss.cpu.brand.addAll(brand);
} catch (e, s) { } catch (e, s) {
Loggers.app.warning(e, s); Loggers.app.warning(e, s);
} }

View File

@@ -3,13 +3,6 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppProvider extends ChangeNotifier { class AppProvider extends ChangeNotifier {
int? _newestBuild;
int? get newestBuild => _newestBuild;
set newestBuild(int? build) {
_newestBuild = build;
notifyListeners();
}
BuildContext? ctx; BuildContext? ctx;
bool isWearOS = false; bool isWearOS = false;

View File

@@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; // import 'dart:io';
import 'package:computer/computer.dart'; import 'package:computer/computer.dart';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
@@ -10,8 +10,8 @@ import 'package:server_box/core/utils/ssh_auth.dart';
import 'package:server_box/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/sftp/req.dart'; // import 'package:server_box/data/model/sftp/req.dart';
import 'package:server_box/data/res/provider.dart'; // import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import '../../core/utils/server.dart'; import '../../core/utils/server.dart';
@@ -313,11 +313,10 @@ class ServerProvider extends ChangeNotifier {
_setServerState(s, ServerConn.connected); _setServerState(s, ServerConn.connected);
// Write script to server
// by ssh
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List; final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
try { try {
await s.client?.runForOutput( await s.client!.runForOutput(
ShellFunc.installShellCmd, ShellFunc.installShellCmd,
action: (session) async { action: (session) async {
session.stdin.add(scriptRaw); session.stdin.add(scriptRaw);
@@ -335,40 +334,11 @@ class ServerProvider extends ChangeNotifier {
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} catch (e) { } catch (e) {
Loggers.app.warning('Write script to ${spi.name} by shell', e); final err = e.toString();
TryLimiter.inc(sid);
/// by sftp s.status.err = SSHErr(type: SSHErrType.writeScript, message: err);
final localPath = Paths.doc.joinPath('install.sh'); _setServerState(s, ServerConn.failed);
final file = File(localPath); Loggers.app.warning('Write script to ${spi.name} by shell', err);
try {
file.writeAsBytes(scriptRaw);
final completer = Completer();
final homePath = (await s.client?.run('echo \$HOME').string)?.trim();
if (homePath == null || homePath.isEmpty) {
throw Exception('Got empty home path');
}
final remotePath = ShellFunc.getShellPath(homePath);
final reqId = Pros.sftp.add(
SftpReq(spi, remotePath, localPath, SftpReqType.upload),
completer: completer,
);
await completer.future;
final err = Pros.sftp.get(reqId)?.error;
if (err != null) {
throw err;
}
} catch (ee) {
TryLimiter.inc(sid);
s.status.err = SSHErr(
type: SSHErrType.writeScript,
message: '$e\n\n$ee',
);
_setServerState(s, ServerConn.failed);
Loggers.app.warning('Write script to ${spi.name} by sftp', ee);
return;
} finally {
if (await file.exists()) await file.delete();
}
} }
} }
@@ -458,26 +428,4 @@ class ServerProvider extends ChangeNotifier {
// reset try times only after prepared successfully // reset try times only after prepared successfully
TryLimiter.reset(sid); TryLimiter.reset(sid);
} }
// Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
// final server = _servers[id];
// if (server == null) return null;
// final watch = Stopwatch()..start();
// final result = await server.client?.run(snippet.fmtWithArgs(server.spi)).string;
// final time = watch.elapsed;
// watch.stop();
// if (result == null) return null;
// return SnippetResult(
// dest: _servers[id]?.spi.name,
// result: result,
// time: time,
// );
// }
// Future<List<SnippetResult?>> runSnippetsMulti(
// List<String> ids,
// Snippet snippet,
// ) async {
// return await Future.wait(ids.map((id) async => runSnippet(id, snippet)));
// }
} }

View File

@@ -2,6 +2,6 @@
class BuildData { class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 992; static const int build = 1034;
static const int script = 49; static const int script = 54;
} }

View File

@@ -9,6 +9,7 @@ abstract final class GithubIds {
'azkadev', 'azkadev',
'kalashnikov', 'kalashnikov',
'calvinweb', 'calvinweb',
'No06',
'QazCetelic', 'QazCetelic',
'RainSunMe', 'RainSunMe',
'FrancXPT', 'FrancXPT',
@@ -20,6 +21,7 @@ abstract final class GithubIds {
'jaychoubaby', 'jaychoubaby',
'fecture', 'fecture',
'Tao173', 'Tao173',
'Jasonzhu1207',
'QingAnLe', 'QingAnLe',
'wxdjs', 'wxdjs',
'Aeorq', 'Aeorq',
@@ -72,7 +74,6 @@ abstract final class GithubIds {
'pgs666', 'pgs666',
'FHU-yezi', 'FHU-yezi',
'ZRY233', 'ZRY233',
'Jasonzhu1207',
'sakuraanzu', 'sakuraanzu',
'licaon-kter', 'licaon-kter',
'77160860', '77160860',
@@ -84,6 +85,16 @@ abstract final class GithubIds {
'Mooling0602', 'Mooling0602',
'IllTamer', 'IllTamer',
'marlkiller', 'marlkiller',
'hlarc',
'itsandrewpao',
'StudyingLover',
'QJAG1024',
'Wuming-HUST',
'WolfCanglong',
'liwenjie119',
'logce',
'h-lyf',
'88484396',
}; };
} }

View File

@@ -280,7 +280,13 @@ class SettingStore extends PersistentStore {
/// Format: {width}x{height} /// Format: {width}x{height}
late final windowSize = property('windowSize', ''); late final windowSize = property('windowSize', '');
late final showIntro = property('showIntro', true); late final introVer = property('introVer', 0);
late final letterCache = property('letterCache', false);
/// Set it to `$EDITOR`, `vim` and etc. to use remote system editor in SSH terminal.
/// Set it empty to use local editor GUI.
late final sftpEditor = property('sftpEditor', '');
// Never show these settings for users // Never show these settings for users
// //

View File

@@ -1,39 +1,60 @@
part of 'app.dart'; part of 'app.dart';
final class _IntroPage extends StatelessWidget { final class _IntroPage extends StatelessWidget {
const _IntroPage(); final List<IntroPageBuilder> pages;
static final _setting = Stores.setting; const _IntroPage(this.pages);
static const _kIconSize = 23.0;
static const _introListPad = EdgeInsets.symmetric(horizontal: 17); static const _builders = {
1: _buildAppSettings,
2: _buildRecommended,
1006: _buildTermLetterCache,
};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, cons) { builder: (context, cons) {
final padTop = cons.maxHeight * .2; final padTop = cons.maxHeight * .16;
final pages_ = pages.map((e) => e(context, padTop)).toList();
return IntroPage( return IntroPage(
pages: [ args: IntroPageArgs(
_buildAppSettings(context, padTop), pages: pages_,
_buildRecommended(context, padTop), onDone: (ctx) {
], Stores.setting.introVer.put(BuildData.build);
onDone: (ctx) { Navigator.of(ctx).pushReplacement(
Stores.setting.showIntro.put(false); MaterialPageRoute(builder: (_) => const HomePage()),
Navigator.of(ctx).pushReplacement( );
MaterialPageRoute(builder: (_) => const HomePage()), },
); ),
},
); );
}, },
); );
} }
Widget _buildRecommended(BuildContext context, double padTop) { static Widget _buildTermLetterCache(BuildContext context, double padTop) {
return ListView( return ListView(
padding: _introListPad, padding: _introListPad,
children: [ children: [
SizedBox(height: padTop), SizedBox(height: padTop),
const Icon(Bootstrap.stars, size: 35), IntroPage.title(icon: BoxIcons.bxs_terminal, big: true),
SizedBox(height: padTop),
ListTile(
leading: const Icon(Bootstrap.alphabet),
title: Text(l10n.letterCache),
subtitle: Text(l10n.letterCacheTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.letterCache),
).cardx,
],
);
}
static Widget _buildRecommended(BuildContext context, double padTop) {
return ListView(
padding: _introListPad,
children: [
SizedBox(height: padTop),
IntroPage.title(icon: Bootstrap.stars, big: true),
SizedBox(height: padTop), SizedBox(height: padTop),
ListTile( ListTile(
leading: const Icon(MingCute.delete_2_fill), leading: const Icon(MingCute.delete_2_fill),
@@ -42,14 +63,14 @@ final class _IntroPage extends StatelessWidget {
trailing: StoreSwitch(prop: _setting.sftpRmrDir), trailing: StoreSwitch(prop: _setting.sftpRmrDir),
).cardx, ).cardx,
ListTile( ListTile(
leading: const Icon(IonIcons.stats_chart, size: _kIconSize), leading: const Icon(MingCute.chart_line_line, size: _kIconSize),
title: Text(l10n.parseContainerStats), title: Text(l10n.stat),
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey), subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.containerParseStat), trailing: StoreSwitch(prop: _setting.containerParseStat),
).cardx, ).cardx,
ListTile( ListTile(
leading: const Icon(OctIcons.cpu), leading: const Icon(OctIcons.cpu),
title: Text('CPU ${l10n.noLineChart}'), title: Text(l10n.noLineChartForCpu),
subtitle: Text(l10n.cpuViewAsProgressTip, style: UIs.textGrey), subtitle: Text(l10n.cpuViewAsProgressTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.cpuViewAsProgress), trailing: StoreSwitch(prop: _setting.cpuViewAsProgress),
).cardx, ).cardx,
@@ -57,12 +78,12 @@ final class _IntroPage extends StatelessWidget {
); );
} }
Widget _buildAppSettings(BuildContext ctx, double padTop) { static Widget _buildAppSettings(BuildContext ctx, double padTop) {
return ListView( return ListView(
padding: _introListPad, padding: _introListPad,
children: [ children: [
SizedBox(height: padTop), SizedBox(height: padTop),
_buildTitle(l10n.init, big: true), IntroPage.title(text: l10n.init, big: true),
SizedBox(height: padTop), SizedBox(height: padTop),
ListTile( ListTile(
leading: const Icon(IonIcons.language), leading: const Icon(IonIcons.language),
@@ -71,12 +92,12 @@ final class _IntroPage extends StatelessWidget {
final selected = await ctx.showPickSingleDialog( final selected = await ctx.showPickSingleDialog(
title: l10n.language, title: l10n.language,
items: AppLocalizations.supportedLocales, items: AppLocalizations.supportedLocales,
name: (p0) => p0.code, name: (p0) => '${p0.nativeDisplayLanguage} (${p0.code})',
initial: _setting.locale.fetch().toLocale, initial: _setting.locale.fetch().toLocale,
); );
if (selected != null) { if (selected != null) {
_setting.locale.put(selected.code); _setting.locale.put(selected.code);
RNodes.app.build(); RNodes.app.notify();
} }
}, },
trailing: Text( trailing: Text(
@@ -94,14 +115,15 @@ final class _IntroPage extends StatelessWidget {
); );
} }
Widget _buildTitle(String text, {bool big = false}) { static List<IntroPageBuilder> get builders {
return Center( final storedVer = _setting.introVer.fetch();
child: Text( return _builders.entries
text, .where((e) => e.key > storedVer)
style: big .map((e) => e.value)
? const TextStyle(fontSize: 41, fontWeight: FontWeight.w500) .toList();
: UIs.textGrey,
),
);
} }
static final _setting = Stores.setting;
static const _kIconSize = 23.0;
static const _introListPad = EdgeInsets.symmetric(horizontal: 17);
} }

View File

@@ -11,7 +11,6 @@
"addr": "Adresse", "addr": "Adresse",
"all": "Alle", "all": "Alle",
"alreadyLastDir": "Bereits im letzten Verzeichnis.", "alreadyLastDir": "Bereits im letzten Verzeichnis.",
"alterUrl": "Url ändern",
"askContinue": "{msg}. Weiter?", "askContinue": "{msg}. Weiter?",
"attention": "Achtung", "attention": "Achtung",
"authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.", "authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.",
@@ -26,7 +25,6 @@
"backupTip": "Das Backup wird nur einfach verschlüsselt.\nBitte bewahre die Datei sicher auf.", "backupTip": "Das Backup wird nur einfach verschlüsselt.\nBitte bewahre die Datei sicher auf.",
"backupVersionNotMatch": "Die Backup-Version stimmt nicht überein.", "backupVersionNotMatch": "Die Backup-Version stimmt nicht überein.",
"battery": "Batterie", "battery": "Batterie",
"beforeConnect": "ServerBox wird nach der Verbindung ein Skript in `~/.config/server_box` schreiben und ausführen. Für weitere technische Details besuchen Sie bitte [Github]({url}).",
"bgRun": "Hintergrundaktualisierung", "bgRun": "Hintergrundaktualisierung",
"bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".", "bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".",
"bioAuth": "Biozertifizierung", "bioAuth": "Biozertifizierung",
@@ -69,7 +67,6 @@
"decode": "Decode", "decode": "Decode",
"decompress": "Dekomprimieren", "decompress": "Dekomprimieren",
"delete": "Löschen", "delete": "Löschen",
"deleteScripts": "Gleichzeitiges Löschen von Server-Skripten",
"deleteServers": "Batch-Löschung von Servern", "deleteServers": "Batch-Löschung von Servern",
"deviceName": "Gerätename", "deviceName": "Gerätename",
"dirEmpty": "Stelle sicher, dass der Ordner leer ist.", "dirEmpty": "Stelle sicher, dass der Ordner leer ist.",
@@ -95,13 +92,15 @@
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.", "editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.",
"encode": "Encode", "encode": "Encode",
"envVars": "Umgebungsvariable",
"error": "Fehler", "error": "Fehler",
"exampleName": "Servername", "exampleName": "Servername",
"experimentalFeature": "Experimentelles Feature", "experimentalFeature": "Experimentelles Feature",
"export": "Export", "export": "Export",
"extraArgs": "Extra args", "extraArgs": "Extra args",
"failed": "Failed", "failed": "Failed",
"fdroidReleaseTip": "Wenn Sie diese App von Fdroid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.", "fallbackSshDest": "SSH-Fallback-Ziel",
"fdroidReleaseTip": "Wenn Sie diese App von F-Droid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.",
"feedback": "Feedback", "feedback": "Feedback",
"feedbackOnGithub": "Wenn du Fragen hast, stelle diese bitte auf Github.", "feedbackOnGithub": "Wenn du Fragen hast, stelle diese bitte auf Github.",
"fieldMustNotEmpty": "Die Eingabefelder dürfen nicht leer sein.", "fieldMustNotEmpty": "Die Eingabefelder dürfen nicht leer sein.",
@@ -155,6 +154,8 @@
"languageName": "Deutsch", "languageName": "Deutsch",
"lastTry": "Letzter Versuch", "lastTry": "Letzter Versuch",
"launchPage": "Startseite", "launchPage": "Startseite",
"letterCache": "Buchstaben-Caching",
"letterCacheTip": "Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.",
"license": "Lizenzen", "license": "Lizenzen",
"light": "Hell", "light": "Hell",
"loadingFiles": "Lädt Dateien...", "loadingFiles": "Lädt Dateien...",
@@ -175,12 +176,13 @@
"name": "Name", "name": "Name",
"needHomeDir": "Wenn Sie ein Synology-Benutzer sind, [sehen Sie hier](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Benutzer anderer Systeme müssen suchen, wie man ein Home-Verzeichnis erstellt.", "needHomeDir": "Wenn Sie ein Synology-Benutzer sind, [sehen Sie hier](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Benutzer anderer Systeme müssen suchen, wie man ein Home-Verzeichnis erstellt.",
"needRestart": "App muss neugestartet werden", "needRestart": "App muss neugestartet werden",
"net": "Netz", "net": "Netzwerk",
"netViewType": "Netzwerkansicht Typ", "netViewType": "Netzwerkansicht Typ",
"newContainer": "Neuer Container", "newContainer": "Neuer Container",
"noClient": "Kein Client", "noClient": "Kein Client",
"noInterface": "Kein Interface", "noInterface": "Kein Interface",
"noLineChart": "Verwenden Sie keine Liniendiagramme", "noLineChart": "Verwenden Sie keine Liniendiagramme",
"noLineChartForCpu": "Verwenden Sie keine Liniendiagramme für CPU",
"noNotiPerm": "Keine Benachrichtigungsrechte, möglicherweise keine Fortschrittsanzeige beim Herunterladen von App-Updates.", "noNotiPerm": "Keine Benachrichtigungsrechte, möglicherweise keine Fortschrittsanzeige beim Herunterladen von App-Updates.",
"noOptions": "Keine Optionen verfügbar", "noOptions": "Keine Optionen verfügbar",
"noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.", "noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.",
@@ -203,11 +205,11 @@
"open": "Öffnen", "open": "Öffnen",
"openLastPath": "Öffnen Sie den letzten Pfad", "openLastPath": "Öffnen Sie den letzten Pfad",
"openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang", "openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang",
"parseContainerStats": "Den Status der Container-Belegung analysieren",
"parseContainerStatsTip": "Das Analysieren des Belegungsstatus durch Docker ist relativ langsam", "parseContainerStatsTip": "Das Analysieren des Belegungsstatus durch Docker ist relativ langsam",
"paste": "Einfügen", "paste": "Einfügen",
"path": "Pfad", "path": "Pfad",
"percentOfSize": "{percent}% von {size}", "percentOfSize": "{percent}% von {size}",
"permission": "Berechtigungen",
"pickFile": "Datei wählen", "pickFile": "Datei wählen",
"pingAvg": "Avg:", "pingAvg": "Avg:",
"pingInputIP": "Bitte gib eine Ziel-IP/Domain ein.", "pingInputIP": "Bitte gib eine Ziel-IP/Domain ein.",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Unbekannter Status", "serverTabUnkown": "Unbekannter Status",
"setting": "Einstellungen", "setting": "Einstellungen",
"sftpDlPrepare": "Verbindung vorbereiten...", "sftpDlPrepare": "Verbindung vorbereiten...",
"sftpEditorTip": "Wenn leer, verwenden Sie den im App integrierten Dateieditor. Wenn ein Wert vorhanden ist, wird der Editor des Remote-Servers verwendet, z.B. `vim` (es wird empfohlen, automatisch gemäß `EDITOR` zu ermitteln).",
"sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.", "sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.",
"sftpSSHConnected": "SFTP Verbunden", "sftpSSHConnected": "SFTP Verbunden",
"sftpShowFoldersFirst": "Ordner zuerst anzeigen", "sftpShowFoldersFirst": "Ordner zuerst anzeigen",
@@ -276,6 +279,7 @@
"sshTip": "Diese Funktion befindet sich jetzt in der Experimentierphase.\n\nBitte melde Bugs auf {url} oder mach mit bei der Entwicklung.", "sshTip": "Diese Funktion befindet sich jetzt in der Experimentierphase.\n\nBitte melde Bugs auf {url} oder mach mit bei der Entwicklung.",
"sshVirtualKeyAutoOff": "Automatische Umschaltung der virtuellen Tasten", "sshVirtualKeyAutoOff": "Automatische Umschaltung der virtuellen Tasten",
"start": "Start", "start": "Start",
"stat": "Statistik",
"stats": "Statistik", "stats": "Statistik",
"stop": "Stop", "stop": "Stop",
"stopped": "Ausgelaufen", "stopped": "Ausgelaufen",
@@ -302,7 +306,7 @@
"total": "Total", "total": "Total",
"traffic": "Durchflussmenge", "traffic": "Durchflussmenge",
"trySudo": "Versuche es mit sudo", "trySudo": "Versuche es mit sudo",
"ttl": "ttl", "ttl": "TTL",
"unknown": "Unbekannt", "unknown": "Unbekannt",
"unknownError": "Unbekannter Fehler", "unknownError": "Unbekannter Fehler",
"unkownConvertMode": "Unbekannter Konvertierungsmodus", "unkownConvertMode": "Unbekannter Konvertierungsmodus",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Wird sofort angewendet", "willTakEeffectImmediately": "Wird sofort angewendet",
"wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.", "wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.",
"write": "Schreiben", "write": "Schreiben",
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht." "writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in ~/.config/server_box geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen."
} }

View File

@@ -2,33 +2,31 @@
"@@locale": "en", "@@locale": "en",
"about": "About", "about": "About",
"aboutThanks": "Thanks to the following people who participated in.", "aboutThanks": "Thanks to the following people who participated in.",
"acceptBeta": "Accept test version updates", "acceptBeta": "Accept beta version updates",
"add": "Add", "add": "Add",
"addAServer": "add a server", "addAServer": "add a server",
"addPrivateKey": "Add private key", "addPrivateKey": "Add private key",
"addSystemPrivateKeyTip": "Currently don't have any private key, do you add the one that comes with the system (~/.ssh/id_rsa)?", "addSystemPrivateKeyTip": "Currently private keys don't exist, do you want to add the one that comes with the system (~/.ssh/id_rsa)?",
"added2List": "Added to task list", "added2List": "Added to task list",
"addr": "Address", "addr": "Address",
"all": "All", "all": "All",
"alreadyLastDir": "Already in last directory.", "alreadyLastDir": "Already in last directory.",
"alterUrl": "Alter url",
"askContinue": "{msg}. Continue?", "askContinue": "{msg}. Continue?",
"attention": "Attention", "attention": "Attention",
"authFailTip": "Authentication failed, please check if the password/key/host/user, etc., are incorrect.", "authFailTip": "Authentication failed, please check whether credentials are correct",
"authRequired": "Auth required", "authRequired": "Auth required",
"auto": "Auto", "auto": "Auto",
"autoBackupConflict": "Only one automatic backup can be turned on at the same time.", "autoBackupConflict": "Only one automatic backup can be turned on at the same time.",
"autoCheckUpdate": "Auto check update", "autoCheckUpdate": "Automatic update check",
"autoConnect": "Auto connect", "autoConnect": "Auto connect",
"autoRun": "Automatic Run", "autoRun": "Auto run",
"autoUpdateHomeWidget": "Auto update home widget", "autoUpdateHomeWidget": "Automatic home widget update",
"backup": "Backup", "backup": "Backup",
"backupTip": "The exported data is simply encrypted. \nPlease keep it safe.", "backupTip": "The exported data is weakly encrypted. \nPlease keep it safe.",
"backupVersionNotMatch": "Backup version is not match.", "backupVersionNotMatch": "Backup version is not match.",
"battery": "Battery", "battery": "Battery",
"beforeConnect": "ServerBox will write and execute a script in `~/.config/server_box` after connection. For more technical details, please visit [Github]({url}).", "bgRun": "Run in background",
"bgRun": "Run in backgroud", "bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".",
"bgRunTip": "This switch only means the program will try to run in the background, whether it can run in the background depends on whether the permission is enabled or not. For native Android, please disable \"Battery Optimization\" in this app, and for miui, please change the power saving policy to \"Unlimited\".",
"bioAuth": "Biometric auth", "bioAuth": "Biometric auth",
"browser": "Browser", "browser": "Browser",
"bulkImportServers": "Batch import servers", "bulkImportServers": "Batch import servers",
@@ -56,7 +54,7 @@
"convert": "Convert", "convert": "Convert",
"copy": "Copy", "copy": "Copy",
"copyPath": "Copy path", "copyPath": "Copy path",
"cpuViewAsProgressTip": "Display the usage rate of each CPU in a progress bar style (old style)", "cpuViewAsProgressTip": "Display the usage of each CPU in a progress bar style (old style)",
"createFile": "Create file", "createFile": "Create file",
"createFolder": "Create folder", "createFolder": "Create folder",
"cursorType": "Cursor type", "cursorType": "Cursor type",
@@ -69,10 +67,9 @@
"decode": "Decode", "decode": "Decode",
"decompress": "Decompress", "decompress": "Decompress",
"delete": "Delete", "delete": "Delete",
"deleteScripts": "Delete server scripts at the same time",
"deleteServers": "Batch delete servers", "deleteServers": "Batch delete servers",
"deviceName": "Device name", "deviceName": "Device name",
"dirEmpty": "Make sure dir is empty.", "dirEmpty": "Make sure the folder is empty.",
"disabled": "Disabled", "disabled": "Disabled",
"disconnected": "Disconnected", "disconnected": "Disconnected",
"disk": "Disk", "disk": "Disk",
@@ -93,17 +90,19 @@
"edit": "Edit", "edit": "Edit",
"editVirtKeys": "Edit virtual keys", "editVirtKeys": "Edit virtual keys",
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "The current code highlighting performance is worse and can be optionally turned off to improve.", "editorHighlightTip": "The current code highlighting performance is not ideal and can be optionally turned off to improve.",
"encode": "Encode", "encode": "Encode",
"envVars": "Environment variable",
"error": "Error", "error": "Error",
"exampleName": "Example name", "exampleName": "Example name",
"experimentalFeature": "Experimental feature", "experimentalFeature": "Experimental feature",
"export": "Export", "export": "Export",
"extraArgs": "Extra args", "extraArgs": "Extra arguments",
"failed": "Failed", "failed": "Failed",
"fdroidReleaseTip": "If you downloaded this app from Fdroid, it is recommended to turn off this option.", "fallbackSshDest": "Fallback SSH destination",
"fdroidReleaseTip": "If you downloaded this app from F-Droid, it is recommended to turn off this option.",
"feedback": "Feedback", "feedback": "Feedback",
"feedbackOnGithub": "If you have any questions, please feedback on Github.", "feedbackOnGithub": "If you have any questions, please create issues on Github.",
"fieldMustNotEmpty": "These fields must not be empty.", "fieldMustNotEmpty": "These fields must not be empty.",
"fileNotExist": "{file} not exist", "fileNotExist": "{file} not exist",
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}", "fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
@@ -125,7 +124,7 @@
"goto": "Go to", "goto": "Go to",
"hideTitleBar": "Hide title bar", "hideTitleBar": "Hide title bar",
"hideTitleBarTip": "After turning it on, please hold down the three buttons in the top right corner to drag.", "hideTitleBarTip": "After turning it on, please hold down the three buttons in the top right corner to drag.",
"highlight": "Code highlight", "highlight": "Code highlighting",
"homeWidgetUrlConfig": "Config home widget url", "homeWidgetUrlConfig": "Config home widget url",
"host": "Host", "host": "Host",
"hour": "Hour", "hour": "Hour",
@@ -155,6 +154,8 @@
"languageName": "English", "languageName": "English",
"lastTry": "Last try", "lastTry": "Last try",
"launchPage": "Launch page", "launchPage": "Launch page",
"letterCache": "Letter caching",
"letterCacheTip": "Recommended to disable, but after disabling, it will be impossible to input CJK characters.",
"license": "License", "license": "License",
"light": "Light", "light": "Light",
"loadingFiles": "Loading files...", "loadingFiles": "Loading files...",
@@ -164,7 +165,7 @@
"madeWithLove": "Made with ❤️ by {myGithub}", "madeWithLove": "Made with ❤️ by {myGithub}",
"manual": "Manual", "manual": "Manual",
"max": "max", "max": "max",
"maxRetryCount": "Number of server reconnection", "maxRetryCount": "Number of server reconnections",
"maxRetryCountEqual0": "Will retry again and again.", "maxRetryCountEqual0": "Will retry again and again.",
"min": "min", "min": "min",
"minute": "Minute", "minute": "Minute",
@@ -174,13 +175,14 @@
"ms": "ms", "ms": "ms",
"name": "Name", "name": "Name",
"needHomeDir": "If you are a Synology user, [see here](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Users of other systems need to search for how to create a home directory.", "needHomeDir": "If you are a Synology user, [see here](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Users of other systems need to search for how to create a home directory.",
"needRestart": "Need to restart app", "needRestart": "App needs to be restarted",
"net": "Net", "net": "Network",
"netViewType": "Net view type", "netViewType": "Network view type",
"newContainer": "New container", "newContainer": "New container",
"noClient": "No client", "noClient": "No client",
"noInterface": "No interface", "noInterface": "No interface",
"noLineChart": "Do not use line charts", "noLineChart": "Do not use line charts",
"noLineChartForCpu": "Do not use line charts for CPU",
"noNotiPerm": "No notification permissions, possibly no progress indication when downloading app updates.", "noNotiPerm": "No notification permissions, possibly no progress indication when downloading app updates.",
"noOptions": "No options", "noOptions": "No options",
"noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.", "noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.",
@@ -199,22 +201,22 @@
"ok": "OK", "ok": "OK",
"onServerDetailPage": "On server detail page", "onServerDetailPage": "On server detail page",
"onlyOneLine": "Only display as one line (scrollable)", "onlyOneLine": "Only display as one line (scrollable)",
"onlyWhenCoreBiggerThan8": "Works only when the number of cores > 8", "onlyWhenCoreBiggerThan8": "Works only when the number of cores is greater than 8",
"open": "Open", "open": "Open",
"openLastPath": "Open the last path", "openLastPath": "Open the last path",
"openLastPathTip": "Different servers will have different logs, and the log is the path to the exit", "openLastPathTip": "Different servers will have different logs, and the log is the path to the exit",
"parseContainerStats": "Parse the container occupancy status", "parseContainerStatsTip": "Parsing the occupancy status of Docker is relatively slow.",
"parseContainerStatsTip": "Docker parsing the occupancy status is relatively slow.",
"paste": "Paste", "paste": "Paste",
"path": "Path", "path": "Path",
"percentOfSize": "{percent}% of {size}", "percentOfSize": "{percent}% of {size}",
"permission": "Permissions",
"pickFile": "Pick file", "pickFile": "Pick file",
"pingAvg": "Avg:", "pingAvg": "Avg:",
"pingInputIP": "Please input a target IP / domain.", "pingInputIP": "Please input a target IP / domain.",
"pingNoServer": "No server to ping.\nPlease add a server in server tab.", "pingNoServer": "No server to ping.\nPlease add a server in server tab.",
"pkg": "Pkg", "pkg": "Pkg",
"pkgUpgradeTip": "Please backup your system before updating.", "pkgUpgradeTip": "Please backup your system before updating.",
"platformNotSupportUpdate": "Current platform does not support in app update.\nPlease build from source and install it.", "platformNotSupportUpdate": "Current platform does not support in-app update.\nPlease build from source and install it.",
"plugInType": "Insertion Type", "plugInType": "Insertion Type",
"plzEnterHost": "Please enter host.", "plzEnterHost": "Please enter host.",
"plzSelectKey": "Please select a key.", "plzSelectKey": "Please select a key.",
@@ -252,7 +254,7 @@
"sequence": "Sequence", "sequence": "Sequence",
"server": "Server", "server": "Server",
"serverDetailOrder": "Detail page widget order", "serverDetailOrder": "Detail page widget order",
"serverFuncBtns": "Server func buttons", "serverFuncBtns": "Server function buttons",
"serverOrder": "Server order", "serverOrder": "Server order",
"serverTabConnecting": "Connecting...", "serverTabConnecting": "Connecting...",
"serverTabEmpty": "There is no server.\nClick the fab to add one.", "serverTabEmpty": "There is no server.\nClick the fab to add one.",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Unknown state", "serverTabUnkown": "Unknown state",
"setting": "Settings", "setting": "Settings",
"sftpDlPrepare": "Preparing to connect...", "sftpDlPrepare": "Preparing to connect...",
"sftpEditorTip": "If empty, use the built-in file editor of the app. If a value is present, use the remote servers editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).",
"sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.", "sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.",
"sftpSSHConnected": "SFTP Connected", "sftpSSHConnected": "SFTP Connected",
"sftpShowFoldersFirst": "Display folders first", "sftpShowFoldersFirst": "Display folders first",
@@ -276,14 +279,15 @@
"sshTip": "This function is now in the experimental stage.\n\nPlease report bugs on {url} or join our development.", "sshTip": "This function is now in the experimental stage.\n\nPlease report bugs on {url} or join our development.",
"sshVirtualKeyAutoOff": "Auto switching of virtual keys", "sshVirtualKeyAutoOff": "Auto switching of virtual keys",
"start": "Start", "start": "Start",
"stats": "Stats", "stat": "Statistics",
"stats": "Statistics",
"stop": "Stop", "stop": "Stop",
"stopped": "Stopped", "stopped": "Stopped",
"storage": "Storage", "storage": "Storage",
"success": "Success", "success": "Success",
"supportFmtArgs": "The following formatting parameters are supported:", "supportFmtArgs": "The following formatting parameters are supported:",
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "The suspend function requires root privileges and systemd support.", "suspendTip": "The suspend function requires root permission and systemd support.",
"switchTo": "Switch to {val}", "switchTo": "Switch to {val}",
"sync": "Sync", "sync": "Sync",
"syncTip": "A restart may be required for some changes to take effect.", "syncTip": "A restart may be required for some changes to take effect.",
@@ -302,10 +306,10 @@
"total": "Total", "total": "Total",
"traffic": "Traffic", "traffic": "Traffic",
"trySudo": "Try using sudo", "trySudo": "Try using sudo",
"ttl": "ttl", "ttl": "TTL",
"unknown": "Unknown", "unknown": "Unknown",
"unknownError": "Unknown error", "unknownError": "Unknown error",
"unkownConvertMode": "Unknown convert mode", "unkownConvertMode": "Unknown conversion mode",
"update": "Update", "update": "Update",
"updateAll": "Update all", "updateAll": "Update all",
"updateIntervalEqual0": "You set to 0, will not update automatically.\nCan't calculate CPU status.", "updateIntervalEqual0": "You set to 0, will not update automatically.\nCan't calculate CPU status.",
@@ -319,7 +323,7 @@
"useCdn": "Using CDN", "useCdn": "Using CDN",
"useCdnTip": "Non-Chinese users are recommended to use CDN. Would you like to use it?", "useCdnTip": "Non-Chinese users are recommended to use CDN. Would you like to use it?",
"useNoPwd": "No password will be used", "useNoPwd": "No password will be used",
"usePodmanByDefault": "Defaulting to Podman", "usePodmanByDefault": "Use Podman by default",
"used": "Used", "used": "Used",
"user": "User", "user": "User",
"versionHaveUpdate": "Found: v1.0.{build}, click to update", "versionHaveUpdate": "Found: v1.0.{build}, click to update",
@@ -327,16 +331,17 @@
"versionUpdated": "Current: v1.0.{build}, is up to date", "versionUpdated": "Current: v1.0.{build}, is up to date",
"view": "View", "view": "View",
"viewErr": "See error", "viewErr": "See error",
"virtKeyHelpClipboard": "Copy to the clipboard if terminal selected is not empty, otherwise paste the contents of the clipboard to the terminal.", "virtKeyHelpClipboard": "Copy to the clipboard if the selected terminal is not empty, otherwise paste the content of the clipboard to the terminal.",
"virtKeyHelpIME": "Turn on/off the keyboard", "virtKeyHelpIME": "Turn on/off the keyboard",
"virtKeyHelpSFTP": "Open current directory in SFTP.", "virtKeyHelpSFTP": "Open current directory in SFTP.",
"waitConnection": "Please wait for the connection to be established.", "waitConnection": "Please wait for the connection to be established.",
"wakeLock": "Keep awake", "wakeLock": "Keep awake",
"watchNotPaired": "No paired Apple Watch", "watchNotPaired": "No paired Apple Watch",
"webdavSettingEmpty": "Webdav setting is empty", "webdavSettingEmpty": "WebDav setting is empty",
"whenOpenApp": "When opening the app", "whenOpenApp": "When opening the app",
"willTakEeffectImmediately": "Will take effect immediately", "willTakEeffectImmediately": "Will take effect immediately",
"wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.", "wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.",
"write": "Write", "write": "Write",
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist." "writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
"writeScriptTip": "After connecting to the server, a script will be written to ~/.config/server_box to monitor the system status. You can review the script content."
} }

View File

@@ -11,7 +11,6 @@
"addr": "Dirección", "addr": "Dirección",
"all": "Todos", "all": "Todos",
"alreadyLastDir": "Ya estás en el directorio superior", "alreadyLastDir": "Ya estás en el directorio superior",
"alterUrl": "URL alternativa",
"askContinue": "{msg}, ¿continuar?", "askContinue": "{msg}, ¿continuar?",
"attention": "Atención", "attention": "Atención",
"authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.", "authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.",
@@ -26,7 +25,6 @@
"backupTip": "Los datos exportados solo están encriptados de manera básica, por favor guárdalos en un lugar seguro.", "backupTip": "Los datos exportados solo están encriptados de manera básica, por favor guárdalos en un lugar seguro.",
"backupVersionNotMatch": "La versión de la copia de seguridad no coincide, no se puede restaurar", "backupVersionNotMatch": "La versión de la copia de seguridad no coincide, no se puede restaurar",
"battery": "Batería", "battery": "Batería",
"beforeConnect": "ServerBox escribirá y ejecutará un script en `~/.config/server_box` después de la conexión. Para más detalles técnicos, por favor visite [Github]({url}).",
"bgRun": "Ejecución en segundo plano", "bgRun": "Ejecución en segundo plano",
"bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.", "bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.",
"bioAuth": "Autenticación biométrica", "bioAuth": "Autenticación biométrica",
@@ -69,7 +67,6 @@
"decode": "Decodificar", "decode": "Decodificar",
"decompress": "Descomprimir", "decompress": "Descomprimir",
"delete": "Eliminar", "delete": "Eliminar",
"deleteScripts": "Eliminar scripts del servidor simultáneamente",
"deleteServers": "Eliminar servidores en lote", "deleteServers": "Eliminar servidores en lote",
"deviceName": "Nombre del dispositivo", "deviceName": "Nombre del dispositivo",
"dirEmpty": "Asegúrate de que el directorio esté vacío", "dirEmpty": "Asegúrate de que el directorio esté vacío",
@@ -95,13 +92,15 @@
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.", "editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.",
"encode": "Codificar", "encode": "Codificar",
"envVars": "Variable de entorno",
"error": "Error", "error": "Error",
"exampleName": "Ejemplo de nombre", "exampleName": "Ejemplo de nombre",
"experimentalFeature": "Función experimental", "experimentalFeature": "Función experimental",
"export": "Exportar", "export": "Exportar",
"extraArgs": "Argumentos extra", "extraArgs": "Argumentos extra",
"failed": "Fallido", "failed": "Fallido",
"fdroidReleaseTip": "Si descargaste esta aplicación desde Fdroid, se recomienda desactivar esta opción.", "fallbackSshDest": "Destino SSH alternativo",
"fdroidReleaseTip": "Si descargaste esta aplicación desde F-Droid, se recomienda desactivar esta opción.",
"feedback": "Retroalimentación", "feedback": "Retroalimentación",
"feedbackOnGithub": "Si tienes algún problema, por favor informa en GitHub", "feedbackOnGithub": "Si tienes algún problema, por favor informa en GitHub",
"fieldMustNotEmpty": "Estos campos no pueden estar vacíos.", "fieldMustNotEmpty": "Estos campos no pueden estar vacíos.",
@@ -155,6 +154,8 @@
"languageName": "Español", "languageName": "Español",
"lastTry": "Último intento", "lastTry": "Último intento",
"launchPage": "Página de lanzamiento", "launchPage": "Página de lanzamiento",
"letterCache": "Caché de letras",
"letterCacheTip": "Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.",
"license": "Licencia de código abierto", "license": "Licencia de código abierto",
"light": "Claro", "light": "Claro",
"loadingFiles": "Cargando directorio...", "loadingFiles": "Cargando directorio...",
@@ -181,6 +182,7 @@
"noClient": "No hay conexión SSH", "noClient": "No hay conexión SSH",
"noInterface": "No hay interfaz disponible", "noInterface": "No hay interfaz disponible",
"noLineChart": "No utilice gráficos de líneas", "noLineChart": "No utilice gráficos de líneas",
"noLineChartForCpu": "No utilice gráficos lineales para la CPU",
"noNotiPerm": "Sin permisos de notificación, posiblemente sin indicación de progreso al descargar actualizaciones de la aplicación.", "noNotiPerm": "Sin permisos de notificación, posiblemente sin indicación de progreso al descargar actualizaciones de la aplicación.",
"noOptions": "Sin opciones disponibles", "noOptions": "Sin opciones disponibles",
"noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.", "noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.",
@@ -203,11 +205,11 @@
"open": "Abrir", "open": "Abrir",
"openLastPath": "Abrir el último camino", "openLastPath": "Abrir el último camino",
"openLastPathTip": "Los diferentes servidores tendrán diferentes registros, y lo que se registra es la ruta de salida", "openLastPathTip": "Los diferentes servidores tendrán diferentes registros, y lo que se registra es la ruta de salida",
"parseContainerStats": "Analizar estado de uso del contenedor",
"parseContainerStatsTip": "El análisis del estado de uso de Docker es bastante lento", "parseContainerStatsTip": "El análisis del estado de uso de Docker es bastante lento",
"paste": "Pegar", "paste": "Pegar",
"path": "Ruta", "path": "Ruta",
"percentOfSize": "El {percent}% de {size}", "percentOfSize": "El {percent}% de {size}",
"permission": "Permisos",
"pickFile": "Seleccionar archivo", "pickFile": "Seleccionar archivo",
"pingAvg": "Promedio:", "pingAvg": "Promedio:",
"pingInputIP": "Por favor, introduce la IP de destino o el dominio", "pingInputIP": "Por favor, introduce la IP de destino o el dominio",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Estado desconocido", "serverTabUnkown": "Estado desconocido",
"setting": "Configuración", "setting": "Configuración",
"sftpDlPrepare": "Preparando para conectar al servidor...", "sftpDlPrepare": "Preparando para conectar al servidor...",
"sftpEditorTip": "Si está vacío, use el editor de archivos incorporado de la aplicación. Si hay un valor, use el editor del servidor remoto, por ejemplo, `vim` (se recomienda detectar automáticamente según `EDITOR`).",
"sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios", "sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios",
"sftpSSHConnected": "SFTP conectado...", "sftpSSHConnected": "SFTP conectado...",
"sftpShowFoldersFirst": "Mostrar carpetas primero", "sftpShowFoldersFirst": "Mostrar carpetas primero",
@@ -276,6 +279,7 @@
"sshTip": "Esta función está en fase de pruebas.\n\nPor favor, informa los problemas en {url}, o únete a nuestro desarrollo.", "sshTip": "Esta función está en fase de pruebas.\n\nPor favor, informa los problemas en {url}, o únete a nuestro desarrollo.",
"sshVirtualKeyAutoOff": "Desactivación automática de teclas virtuales", "sshVirtualKeyAutoOff": "Desactivación automática de teclas virtuales",
"start": "Iniciar", "start": "Iniciar",
"stat": "Estadísticas",
"stats": "Estadísticas", "stats": "Estadísticas",
"stop": "Detener", "stop": "Detener",
"stopped": "Detenido", "stopped": "Detenido",
@@ -302,7 +306,7 @@
"total": "Total", "total": "Total",
"traffic": "Tráfico", "traffic": "Tráfico",
"trySudo": "Intentar con sudo", "trySudo": "Intentar con sudo",
"ttl": "Tiempo de vida (TTL)", "ttl": "TTL",
"unknown": "Desconocido", "unknown": "Desconocido",
"unknownError": "Error desconocido", "unknownError": "Error desconocido",
"unkownConvertMode": "Modo de conversión desconocido", "unkownConvertMode": "Modo de conversión desconocido",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente", "willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente",
"wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.", "wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.",
"write": "Escribir", "write": "Escribir",
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe." "writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
"writeScriptTip": "Después de conectarse al servidor, se escribirá un script en ~/.config/server_box para monitorear el estado del sistema. Puedes revisar el contenido del script."
} }

View File

@@ -11,7 +11,6 @@
"addr": "Adresse", "addr": "Adresse",
"all": "Tous", "all": "Tous",
"alreadyLastDir": "Déjà dans le dernier répertoire.", "alreadyLastDir": "Déjà dans le dernier répertoire.",
"alterUrl": "Modifier l'URL",
"askContinue": "{msg}. Continuer ?", "askContinue": "{msg}. Continuer ?",
"attention": "Attention", "attention": "Attention",
"authFailTip": "Échec de l'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.", "authFailTip": "Échec de l'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.",
@@ -26,7 +25,6 @@
"backupTip": "Les données exportées sont simplement chiffrées. \nVeuillez les garder en sécurité.", "backupTip": "Les données exportées sont simplement chiffrées. \nVeuillez les garder en sécurité.",
"backupVersionNotMatch": "La version de sauvegarde ne correspond pas.", "backupVersionNotMatch": "La version de sauvegarde ne correspond pas.",
"battery": "Batterie", "battery": "Batterie",
"beforeConnect": "ServerBox écrira et exécutera un script dans `~/.config/server_box` après la connexion. Pour plus de détails techniques, veuillez visiter [Github]({url}).",
"bgRun": "Exécution en arrière-plan", "bgRun": "Exécution en arrière-plan",
"bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».", "bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».",
"bioAuth": "Authentification biométrique", "bioAuth": "Authentification biométrique",
@@ -69,7 +67,6 @@
"decode": "Décoder", "decode": "Décoder",
"decompress": "Décompresser", "decompress": "Décompresser",
"delete": "Supprimer", "delete": "Supprimer",
"deleteScripts": "Supprimer les scripts du serveur en même temps",
"deleteServers": "Supprimer des serveurs en lot", "deleteServers": "Supprimer des serveurs en lot",
"deviceName": "Nom de l'appareil", "deviceName": "Nom de l'appareil",
"dirEmpty": "Assurez-vous que le répertoire est vide.", "dirEmpty": "Assurez-vous que le répertoire est vide.",
@@ -95,13 +92,15 @@
"editor": "Éditeur", "editor": "Éditeur",
"editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.", "editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.",
"encode": "Encoder", "encode": "Encoder",
"envVars": "Variable denvironnement",
"error": "Erreur", "error": "Erreur",
"exampleName": "Nom de l'exemple", "exampleName": "Nom de l'exemple",
"experimentalFeature": "Fonctionnalité expérimentale", "experimentalFeature": "Fonctionnalité expérimentale",
"export": "Exporter", "export": "Exporter",
"extraArgs": "Arguments supplémentaires", "extraArgs": "Arguments supplémentaires",
"failed": "Échoué", "failed": "Échoué",
"fdroidReleaseTip": "Si vous avez téléchargé cette application depuis Fdroid, il est recommandé de désactiver cette option.", "fallbackSshDest": "Destino SSH alternativo",
"fdroidReleaseTip": "Si vous avez téléchargé cette application depuis F-Droid, il est recommandé de désactiver cette option.",
"feedback": "Retour", "feedback": "Retour",
"feedbackOnGithub": "Si vous avez des questions, veuillez donner votre avis sur Github.", "feedbackOnGithub": "Si vous avez des questions, veuillez donner votre avis sur Github.",
"fieldMustNotEmpty": "Ces champs ne doivent pas être vides.", "fieldMustNotEmpty": "Ces champs ne doivent pas être vides.",
@@ -155,6 +154,8 @@
"languageName": "Français", "languageName": "Français",
"lastTry": "Dernière tentative", "lastTry": "Dernière tentative",
"launchPage": "Page de lancement", "launchPage": "Page de lancement",
"letterCache": "Mise en cache des lettres",
"letterCacheTip": "Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.",
"license": "Licence", "license": "Licence",
"light": "Clair", "light": "Clair",
"loadingFiles": "Chargement des fichiers...", "loadingFiles": "Chargement des fichiers...",
@@ -181,6 +182,7 @@
"noClient": "Pas de client", "noClient": "Pas de client",
"noInterface": "Pas d'interface", "noInterface": "Pas d'interface",
"noLineChart": "Ne pas utiliser de graphiques linéaires", "noLineChart": "Ne pas utiliser de graphiques linéaires",
"noLineChartForCpu": "Ne pas utiliser de graphiques linéaires pour l'unité centrale",
"noNotiPerm": "Pas de permissions de notification, peut-être pas d'indication de progression lors de la mise à jour des applications.", "noNotiPerm": "Pas de permissions de notification, peut-être pas d'indication de progression lors de la mise à jour des applications.",
"noOptions": "Pas d'options", "noOptions": "Pas d'options",
"noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.", "noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.",
@@ -203,11 +205,11 @@
"open": "Ouvrir", "open": "Ouvrir",
"openLastPath": "Ouvrir le dernier chemin", "openLastPath": "Ouvrir le dernier chemin",
"openLastPathTip": "Les différents serveurs auront des journaux différents, et le journal est le chemin vers la sortie", "openLastPathTip": "Les différents serveurs auront des journaux différents, et le journal est le chemin vers la sortie",
"parseContainerStats": "Analyser l'état d'occupation du conteneur",
"parseContainerStatsTip": "L'analyse de l'occupation des conteneurs Docker est relativement lente.", "parseContainerStatsTip": "L'analyse de l'occupation des conteneurs Docker est relativement lente.",
"paste": "Coller", "paste": "Coller",
"path": "Chemin", "path": "Chemin",
"percentOfSize": "{percent}% de {size}", "percentOfSize": "{percent}% de {size}",
"permission": "Permissions",
"pickFile": "Choisir un fichier", "pickFile": "Choisir un fichier",
"pingAvg": "Moy.:", "pingAvg": "Moy.:",
"pingInputIP": "Veuillez saisir une adresse IP / un domaine cible.", "pingInputIP": "Veuillez saisir une adresse IP / un domaine cible.",
@@ -262,6 +264,7 @@
"serverTabUnkown": "État inconnu", "serverTabUnkown": "État inconnu",
"setting": "Paramètres", "setting": "Paramètres",
"sftpDlPrepare": "Préparation de la connexion...", "sftpDlPrepare": "Préparation de la connexion...",
"sftpEditorTip": "Si vide, utilisez léditeur de fichiers intégré de lapplication. Si une valeur est présente, utilisez léditeur du serveur distant, par exemple `vim` (il est recommandé de détecter automatiquement selon `EDITOR`).",
"sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.", "sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.",
"sftpSSHConnected": "SFTP Connecté", "sftpSSHConnected": "SFTP Connecté",
"sftpShowFoldersFirst": "Afficher d'abord les dossiers", "sftpShowFoldersFirst": "Afficher d'abord les dossiers",
@@ -276,6 +279,7 @@
"sshTip": "Cette fonctionnalité est actuellement à l'étape expérimentale.\n\nVeuillez signaler les bugs sur {url} ou rejoindre notre développement.", "sshTip": "Cette fonctionnalité est actuellement à l'étape expérimentale.\n\nVeuillez signaler les bugs sur {url} ou rejoindre notre développement.",
"sshVirtualKeyAutoOff": "Activation automatique des touches virtuelles", "sshVirtualKeyAutoOff": "Activation automatique des touches virtuelles",
"start": "Démarrer", "start": "Démarrer",
"stat": "Statistiques",
"stats": "Statistiques", "stats": "Statistiques",
"stop": "Arrêter", "stop": "Arrêter",
"stopped": "Arrêté", "stopped": "Arrêté",
@@ -302,7 +306,7 @@
"total": "Total", "total": "Total",
"traffic": "Trafic", "traffic": "Trafic",
"trySudo": "Essayer d'utiliser sudo", "trySudo": "Essayer d'utiliser sudo",
"ttl": "ttl", "ttl": "TTL",
"unknown": "Inconnu", "unknown": "Inconnu",
"unknownError": "Erreur inconnue", "unknownError": "Erreur inconnue",
"unkownConvertMode": "Mode de conversion inconnu", "unkownConvertMode": "Mode de conversion inconnu",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Prendra effet immédiatement", "willTakEeffectImmediately": "Prendra effet immédiatement",
"wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.", "wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.",
"write": "Écrire", "write": "Écrire",
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas." "writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
"writeScriptTip": "Après la connexion au serveur, un script sera écrit dans ~/.config/server_box pour surveiller létat du système. Vous pouvez examiner le contenu du script."
} }

View File

@@ -11,7 +11,6 @@
"addr": "Alamat", "addr": "Alamat",
"all": "Semua", "all": "Semua",
"alreadyLastDir": "Sudah di direktori terakhir.", "alreadyLastDir": "Sudah di direktori terakhir.",
"alterUrl": "Alter url",
"askContinue": "{msg}, lanjutkan?", "askContinue": "{msg}, lanjutkan?",
"attention": "Perhatian", "attention": "Perhatian",
"authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.", "authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.",
@@ -26,7 +25,6 @@
"backupTip": "Data yang diekspor hanya dienkripsi.\nTolong jaga keamanannya.", "backupTip": "Data yang diekspor hanya dienkripsi.\nTolong jaga keamanannya.",
"backupVersionNotMatch": "Versi cadangan tidak cocok.", "backupVersionNotMatch": "Versi cadangan tidak cocok.",
"battery": "Baterai", "battery": "Baterai",
"beforeConnect": "ServerBox akan menulis dan menjalankan skrip di `~/.config/server_box` setelah koneksi. Untuk detail teknis lebih lanjut, silakan kunjungi [Github]({url}).",
"bgRun": "Jalankan di Backgroud", "bgRun": "Jalankan di Backgroud",
"bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".", "bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".",
"bioAuth": "Biosertifikasi", "bioAuth": "Biosertifikasi",
@@ -69,7 +67,6 @@
"decode": "Membaca sandi", "decode": "Membaca sandi",
"decompress": "Dekompresi", "decompress": "Dekompresi",
"delete": "Menghapus", "delete": "Menghapus",
"deleteScripts": "Menghapus skrip server secara bersamaan",
"deleteServers": "Penghapusan server secara batch", "deleteServers": "Penghapusan server secara batch",
"deviceName": "Nama perangkat", "deviceName": "Nama perangkat",
"dirEmpty": "Pastikan dir kosong.", "dirEmpty": "Pastikan dir kosong.",
@@ -95,13 +92,15 @@
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.", "editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.",
"encode": "Menyandi", "encode": "Menyandi",
"envVars": "Variabel lingkungan",
"error": "Kesalahan", "error": "Kesalahan",
"exampleName": "Nama contoh", "exampleName": "Nama contoh",
"experimentalFeature": "Fitur eksperimental", "experimentalFeature": "Fitur eksperimental",
"export": "Ekspor", "export": "Ekspor",
"extraArgs": "Args ekstra", "extraArgs": "Args ekstra",
"failed": "Gagal", "failed": "Gagal",
"fdroidReleaseTip": "Jika Anda mengunduh aplikasi ini dari Fdroid, disarankan untuk mematikan opsi ini.", "fallbackSshDest": "Tujuan SSH mundur",
"fdroidReleaseTip": "Jika Anda mengunduh aplikasi ini dari F-Droid, disarankan untuk mematikan opsi ini.",
"feedback": "Masukan", "feedback": "Masukan",
"feedbackOnGithub": "Jika Anda memiliki pertanyaan, silakan umpan balik tentang GitHub.", "feedbackOnGithub": "Jika Anda memiliki pertanyaan, silakan umpan balik tentang GitHub.",
"fieldMustNotEmpty": "Bidang -bidang ini tidak boleh kosong.", "fieldMustNotEmpty": "Bidang -bidang ini tidak boleh kosong.",
@@ -155,6 +154,8 @@
"languageName": "Indonesia", "languageName": "Indonesia",
"lastTry": "Percobaan terakhir", "lastTry": "Percobaan terakhir",
"launchPage": "Halaman peluncuran", "launchPage": "Halaman peluncuran",
"letterCache": "Caching huruf",
"letterCacheTip": "Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.",
"license": "Lisensi", "license": "Lisensi",
"light": "Terang", "light": "Terang",
"loadingFiles": "Memuat file ...", "loadingFiles": "Memuat file ...",
@@ -175,12 +176,13 @@
"name": "Nama", "name": "Nama",
"needHomeDir": "Jika Anda pengguna Synology, [lihat di sini](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Pengguna sistem lain perlu mencari cara membuat direktori home.", "needHomeDir": "Jika Anda pengguna Synology, [lihat di sini](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Pengguna sistem lain perlu mencari cara membuat direktori home.",
"needRestart": "Perlu memulai ulang aplikasi", "needRestart": "Perlu memulai ulang aplikasi",
"net": "Net", "net": "Jaringan",
"netViewType": "Jenis tampilan bersih", "netViewType": "Jenis tampilan bersih",
"newContainer": "Wadah baru", "newContainer": "Wadah baru",
"noClient": "Tidak ada klien", "noClient": "Tidak ada klien",
"noInterface": "Tidak ada antarmuka", "noInterface": "Tidak ada antarmuka",
"noLineChart": "Jangan gunakan grafik garis", "noLineChart": "Jangan gunakan grafik garis",
"noLineChartForCpu": "Jangan gunakan diagram garis untuk CPU",
"noNotiPerm": "Tidak ada izin notifikasi, mungkin tidak ada indikasi kemajuan saat mengunduh pembaruan aplikasi.", "noNotiPerm": "Tidak ada izin notifikasi, mungkin tidak ada indikasi kemajuan saat mengunduh pembaruan aplikasi.",
"noOptions": "Tidak ada opsi", "noOptions": "Tidak ada opsi",
"noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.", "noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.",
@@ -203,11 +205,11 @@
"open": "Membuka", "open": "Membuka",
"openLastPath": "Buka jalur terakhir", "openLastPath": "Buka jalur terakhir",
"openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar", "openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar",
"parseContainerStats": "Memecahkan status okupansi kontainer",
"parseContainerStatsTip": "Parsing status okupansi oleh Docker agak lambat", "parseContainerStatsTip": "Parsing status okupansi oleh Docker agak lambat",
"paste": "Tempel", "paste": "Tempel",
"path": "Jalur", "path": "Jalur",
"percentOfSize": "{percent}% dari {size}", "percentOfSize": "{percent}% dari {size}",
"permission": "Izin",
"pickFile": "Pilih file", "pickFile": "Pilih file",
"pingAvg": "Rata -rata:", "pingAvg": "Rata -rata:",
"pingInputIP": "Harap masukkan IP / domain target.", "pingInputIP": "Harap masukkan IP / domain target.",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Negara yang tidak diketahui", "serverTabUnkown": "Negara yang tidak diketahui",
"setting": "Pengaturan", "setting": "Pengaturan",
"sftpDlPrepare": "Bersiap untuk terhubung ...", "sftpDlPrepare": "Bersiap untuk terhubung ...",
"sftpEditorTip": "Jika kosong, gunakan editor file bawaan aplikasi. Jika ada nilai, gunakan editor server jarak jauh, misalnya `vim` (disarankan untuk mendeteksi secara otomatis sesuai `EDITOR`).",
"sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP", "sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP",
"sftpSSHConnected": "Sftp terhubung", "sftpSSHConnected": "Sftp terhubung",
"sftpShowFoldersFirst": "Folder ditampilkan lebih dulu", "sftpShowFoldersFirst": "Folder ditampilkan lebih dulu",
@@ -276,6 +279,7 @@
"sshTip": "Fungsi ini sekarang dalam tahap eksperimen.\n\nHarap laporkan bug di {url} atau bergabunglah dengan pengembangan kami.", "sshTip": "Fungsi ini sekarang dalam tahap eksperimen.\n\nHarap laporkan bug di {url} atau bergabunglah dengan pengembangan kami.",
"sshVirtualKeyAutoOff": "Switching Otomatis Kunci Virtual", "sshVirtualKeyAutoOff": "Switching Otomatis Kunci Virtual",
"start": "Awal", "start": "Awal",
"stat": "Statistik",
"stats": "Statistik", "stats": "Statistik",
"stop": "Berhenti", "stop": "Berhenti",
"stopped": "dihentikan", "stopped": "dihentikan",
@@ -302,7 +306,7 @@
"total": "Total", "total": "Total",
"traffic": "Lalu lintas", "traffic": "Lalu lintas",
"trySudo": "Cobalah menggunakan sudo", "trySudo": "Cobalah menggunakan sudo",
"ttl": "ttl", "ttl": "TTL",
"unknown": "Tidak dikenal", "unknown": "Tidak dikenal",
"unknownError": "Kesalahan yang tidak diketahui", "unknownError": "Kesalahan yang tidak diketahui",
"unkownConvertMode": "Mode Konversi Tidak Diketahui", "unkownConvertMode": "Mode Konversi Tidak Diketahui",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Akan segera berlaku", "willTakEeffectImmediately": "Akan segera berlaku",
"wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.", "wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.",
"write": "Tulis", "write": "Tulis",
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada." "writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
"writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke ~/.config/server_box untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut."
} }

View File

@@ -5,13 +5,12 @@
"acceptBeta": "テストバージョンの更新を受け入れる", "acceptBeta": "テストバージョンの更新を受け入れる",
"add": "追加", "add": "追加",
"addAServer": "サーバーを追加する", "addAServer": "サーバーを追加する",
"addPrivateKey": "プライベートキーを追加", "addPrivateKey": "秘密鍵を追加",
"addSystemPrivateKeyTip": "現在プライベートキーがありません。システムのデフォルト(~/.ssh/id_rsa)を追加しますか?", "addSystemPrivateKeyTip": "現在秘密鍵がありません。システムのデフォルト(~/.ssh/id_rsa)を追加しますか?",
"added2List": "タスクリストに追加されました", "added2List": "タスクリストに追加されました",
"addr": "住所", "addr": "アドレス",
"all": "すべて", "all": "すべて",
"alreadyLastDir": "すでに最上位のディレクトリです", "alreadyLastDir": "すでに最上位のディレクトリです",
"alterUrl": "代替リンク",
"askContinue": "{msg}、続行しますか?", "askContinue": "{msg}、続行しますか?",
"attention": "注意", "attention": "注意",
"authFailTip": "認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。", "authFailTip": "認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。",
@@ -26,9 +25,8 @@
"backupTip": "エクスポートされたデータは簡単に暗号化されています。適切に保管してください。", "backupTip": "エクスポートされたデータは簡単に暗号化されています。適切に保管してください。",
"backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません", "backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません",
"battery": "バッテリー", "battery": "バッテリー",
"beforeConnect": "ServerBoxは接続後に`~/.config/server_box`にスクリプトを書き込み実行します。詳細な技術情報については[Github]({url})をご覧ください。",
"bgRun": "バックグラウンド実行", "bgRun": "バックグラウンド実行",
"bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。ネイティブAndroidでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。", "bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。",
"bioAuth": "生体認証", "bioAuth": "生体認証",
"browser": "ブラウザ", "browser": "ブラウザ",
"bulkImportServers": "サーバーを一括インポートする", "bulkImportServers": "サーバーを一括インポートする",
@@ -37,7 +35,7 @@
"cancel": "キャンセル", "cancel": "キャンセル",
"choose": "選択", "choose": "選択",
"chooseFontFile": "フォントファイルを選択", "chooseFontFile": "フォントファイルを選択",
"choosePrivateKey": "プライベートキーを選択", "choosePrivateKey": "秘密鍵を選択",
"clear": "クリア", "clear": "クリア",
"clipboard": "クリップボード", "clipboard": "クリップボード",
"close": "閉じる", "close": "閉じる",
@@ -69,7 +67,6 @@
"decode": "デコード", "decode": "デコード",
"decompress": "解凍", "decompress": "解凍",
"delete": "削除", "delete": "削除",
"deleteScripts": "サーバースクリプトも削除",
"deleteServers": "サーバーを一括削除", "deleteServers": "サーバーを一括削除",
"deviceName": "デバイス名", "deviceName": "デバイス名",
"dirEmpty": "フォルダーが空であることを確認してください", "dirEmpty": "フォルダーが空であることを確認してください",
@@ -95,13 +92,15 @@
"editor": "エディター", "editor": "エディター",
"editorHighlightTip": "現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。", "editorHighlightTip": "現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。",
"encode": "エンコード", "encode": "エンコード",
"envVars": "環境変数",
"error": "エラー", "error": "エラー",
"exampleName": "名前例", "exampleName": "名前例",
"experimentalFeature": "実験的な機能", "experimentalFeature": "実験的な機能",
"export": "エクスポート", "export": "エクスポート",
"extraArgs": "追加引数", "extraArgs": "追加引数",
"failed": "失敗しました", "failed": "失敗しました",
"fdroidReleaseTip": "このアプリをFdroidからダウンロードした場合、このオプションをオフにすることをお勧めします。", "fallbackSshDest": "フォールバックSSH宛先",
"fdroidReleaseTip": "このアプリをF-Droidからダウンロードした場合、このオプションをオフにすることをお勧めします。",
"feedback": "フィードバック", "feedback": "フィードバック",
"feedbackOnGithub": "問題がある場合は、GitHubでフィードバックしてください", "feedbackOnGithub": "問題がある場合は、GitHubでフィードバックしてください",
"fieldMustNotEmpty": "これらの入力フィールドは空にできません。", "fieldMustNotEmpty": "これらの入力フィールドは空にできません。",
@@ -155,6 +154,8 @@
"languageName": "日本語", "languageName": "日本語",
"lastTry": "最後の試み", "lastTry": "最後の試み",
"launchPage": "起動ページ", "launchPage": "起動ページ",
"letterCache": "文字キャッシング",
"letterCacheTip": "無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。",
"license": "オープンソースライセンス", "license": "オープンソースライセンス",
"light": "ライト", "light": "ライト",
"loadingFiles": "ディレクトリを読み込んでいます...", "loadingFiles": "ディレクトリを読み込んでいます...",
@@ -181,12 +182,13 @@
"noClient": "SSH接続がありません", "noClient": "SSH接続がありません",
"noInterface": "使用可能なインターフェースがありません", "noInterface": "使用可能なインターフェースがありません",
"noLineChart": "折れ線グラフを使用しない", "noLineChart": "折れ線グラフを使用しない",
"noLineChartForCpu": "CPUに折れ線グラフを使わない",
"noNotiPerm": "通知の権限がないため、アプリの更新のダウンロード中に進行状況が表示されない場合があります。", "noNotiPerm": "通知の権限がないため、アプリの更新のダウンロード中に進行状況が表示されない場合があります。",
"noOptions": "選択肢がありません", "noOptions": "選択肢がありません",
"noPrivateKeyTip": "私有鍵が存在しません。削除されたか、設定ミスがある可能性があります。", "noPrivateKeyTip": "秘密鍵が存在しません。削除されたか、設定ミスがある可能性があります。",
"noPromptAgain": "再度確認しない", "noPromptAgain": "再度確認しない",
"noResult": "結果なし", "noResult": "結果なし",
"noSavedPrivateKey": "保存されたプライベートキーがありません。", "noSavedPrivateKey": "保存された秘密鍵がありません。",
"noSavedSnippet": "保存されたスニペットがありません。", "noSavedSnippet": "保存されたスニペットがありません。",
"noServerAvailable": "使用可能なサーバーがありません。", "noServerAvailable": "使用可能なサーバーがありません。",
"noTask": "タスクがありません", "noTask": "タスクがありません",
@@ -203,11 +205,11 @@
"open": "開く", "open": "開く",
"openLastPath": "最後のパスを開く", "openLastPath": "最後のパスを開く",
"openLastPathTip": "異なるサーバーには異なる記録があり、記録されているのは退出時のパスです", "openLastPathTip": "異なるサーバーには異なる記録があり、記録されているのは退出時のパスです",
"parseContainerStats": "コンテナ使用状況を解析",
"parseContainerStatsTip": "Dockerの使用状況の解析は比較的遅いです", "parseContainerStatsTip": "Dockerの使用状況の解析は比較的遅いです",
"paste": "貼り付け", "paste": "貼り付け",
"path": "パス", "path": "パス",
"percentOfSize": "{size} の {percent}%", "percentOfSize": "{size} の {percent}%",
"permission": "権限",
"pickFile": "ファイルを選択", "pickFile": "ファイルを選択",
"pingAvg": "平均:", "pingAvg": "平均:",
"pingInputIP": "対象のIPまたはドメインを入力してください", "pingInputIP": "対象のIPまたはドメインを入力してください",
@@ -217,11 +219,11 @@
"platformNotSupportUpdate": "現在のプラットフォームは更新をサポートしていません。最新のソースコードをコンパイルして手動でインストールしてください", "platformNotSupportUpdate": "現在のプラットフォームは更新をサポートしていません。最新のソースコードをコンパイルして手動でインストールしてください",
"plugInType": "挿入タイプ", "plugInType": "挿入タイプ",
"plzEnterHost": "ホストを入力してください", "plzEnterHost": "ホストを入力してください",
"plzSelectKey": "プライベートキーを選択してください", "plzSelectKey": "秘密鍵を選択してください",
"port": "ポート", "port": "ポート",
"preview": "プレビュー", "preview": "プレビュー",
"primaryColorSeed": "プライマリーカラーシード", "primaryColorSeed": "プライマリーカラーシード",
"privateKey": "プライベートキー", "privateKey": "秘密鍵",
"process": "プロセス", "process": "プロセス",
"pushToken": "プッシュトークン", "pushToken": "プッシュトークン",
"pveIgnoreCertTip": "オプションを有効にすることは推奨されません、セキュリティリスクに注意してくださいPVEのデフォルト証明書を使用している場合は、このオプションを有効にする必要があります。", "pveIgnoreCertTip": "オプションを有効にすることは推奨されません、セキュリティリスクに注意してくださいPVEのデフォルト証明書を使用している場合は、このオプションを有効にする必要があります。",
@@ -258,10 +260,11 @@
"serverTabEmpty": "現在サーバーはありません。\n右下のボタンをクリックして追加してください。", "serverTabEmpty": "現在サーバーはありません。\n右下のボタンをクリックして追加してください。",
"serverTabFailed": "失敗", "serverTabFailed": "失敗",
"serverTabLoading": "読み込み中...", "serverTabLoading": "読み込み中...",
"serverTabPlzSave": "このプライベートキーを再保存してください", "serverTabPlzSave": "この秘密鍵を再保存してください",
"serverTabUnkown": "不明な状態", "serverTabUnkown": "不明な状態",
"setting": "設定", "setting": "設定",
"sftpDlPrepare": "サーバーへの接続を準備中...", "sftpDlPrepare": "サーバーへの接続を準備中...",
"sftpEditorTip": "空の場合は、アプリ内蔵のファイルエディタを使用します。値がある場合は、リモートサーバーのエディタ(例:`vim`)を使用します(`EDITOR` に従って自動検出することをお勧めします)。",
"sftpRmrDirSummary": "SFTPで`rm -r`を使用してフォルダーを削除", "sftpRmrDirSummary": "SFTPで`rm -r`を使用してフォルダーを削除",
"sftpSSHConnected": "SFTPに接続されました...", "sftpSSHConnected": "SFTPに接続されました...",
"sftpShowFoldersFirst": "フォルダーを先に表示", "sftpShowFoldersFirst": "フォルダーを先に表示",
@@ -276,6 +279,7 @@
"sshTip": "この機能は現在テスト段階にあります。\n\n問題がある場合は、{url}でフィードバックしてください。", "sshTip": "この機能は現在テスト段階にあります。\n\n問題がある場合は、{url}でフィードバックしてください。",
"sshVirtualKeyAutoOff": "仮想キーの自動オフ", "sshVirtualKeyAutoOff": "仮想キーの自動オフ",
"start": "開始", "start": "開始",
"stat": "統計",
"stats": "統計", "stats": "統計",
"stop": "停止", "stop": "停止",
"stopped": "停止しました", "stopped": "停止しました",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "変更は即座に有効になります", "willTakEeffectImmediately": "変更は即座に有効になります",
"wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。", "wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
"write": "書き込み", "write": "書き込み",
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。" "writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが ~/.config/server_box に書き込まれます。スクリプトの内容を確認できます。"
} }

View File

@@ -11,7 +11,6 @@
"addr": "Adres", "addr": "Adres",
"all": "Alle", "all": "Alle",
"alreadyLastDir": "Al in de laatst gebruikte map.", "alreadyLastDir": "Al in de laatst gebruikte map.",
"alterUrl": "Url wijzigen",
"askContinue": "{msg}. Doorgaan?", "askContinue": "{msg}. Doorgaan?",
"attention": "Let op", "attention": "Let op",
"authFailTip": "Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.", "authFailTip": "Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.",
@@ -26,7 +25,6 @@
"backupTip": "De geëxporteerde gegevens zijn simpelweg versleuteld. \nBewaar deze aub veilig.", "backupTip": "De geëxporteerde gegevens zijn simpelweg versleuteld. \nBewaar deze aub veilig.",
"backupVersionNotMatch": "Back-upversie komt niet overeen.", "backupVersionNotMatch": "Back-upversie komt niet overeen.",
"battery": "Batterij", "battery": "Batterij",
"beforeConnect": "ServerBox zal na verbinding een script schrijven en uitvoeren in `~/.config/server_box`. Voor meer technische details, bezoek [Github]({url}).",
"bgRun": "Uitvoeren op de achtergrond", "bgRun": "Uitvoeren op de achtergrond",
"bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".", "bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".",
"bioAuth": "Biometrische authenticatie", "bioAuth": "Biometrische authenticatie",
@@ -69,7 +67,6 @@
"decode": "Decoderen", "decode": "Decoderen",
"decompress": "Decomprimeren", "decompress": "Decomprimeren",
"delete": "Verwijderen", "delete": "Verwijderen",
"deleteScripts": "Verwijder tegelijkertijd serverscripts",
"deleteServers": "Servers batchgewijs verwijderen", "deleteServers": "Servers batchgewijs verwijderen",
"deviceName": "Apparaatnaam", "deviceName": "Apparaatnaam",
"dirEmpty": "Zorg ervoor dat de map leeg is.", "dirEmpty": "Zorg ervoor dat de map leeg is.",
@@ -95,13 +92,15 @@
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.", "editorHighlightTip": "De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.",
"encode": "Coderen", "encode": "Coderen",
"envVars": "Omgevingsvariabele",
"error": "Fout", "error": "Fout",
"exampleName": "Voorbeeldnaam", "exampleName": "Voorbeeldnaam",
"experimentalFeature": "Experimentele functie", "experimentalFeature": "Experimentele functie",
"export": "Exporteren", "export": "Exporteren",
"extraArgs": "Extra argumenten", "extraArgs": "Extra argumenten",
"failed": "Mislukt", "failed": "Mislukt",
"fdroidReleaseTip": "Als u deze app van Fdroid heeft gedownload, wordt aanbevolen deze optie uit te schakelen.", "fallbackSshDest": "Fallback SSH-bestemming",
"fdroidReleaseTip": "Als u deze app van F-Droid heeft gedownload, wordt aanbevolen deze optie uit te schakelen.",
"feedback": "Feedback", "feedback": "Feedback",
"feedbackOnGithub": "Als je vragen hebt, geef dan feedback op Github.", "feedbackOnGithub": "Als je vragen hebt, geef dan feedback op Github.",
"fieldMustNotEmpty": "Deze velden mogen niet leeg zijn.", "fieldMustNotEmpty": "Deze velden mogen niet leeg zijn.",
@@ -155,6 +154,8 @@
"languageName": "Nederlands", "languageName": "Nederlands",
"lastTry": "Laatste poging", "lastTry": "Laatste poging",
"launchPage": "Startpagina", "launchPage": "Startpagina",
"letterCache": "Lettercaching",
"letterCacheTip": "Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.",
"license": "Licentie", "license": "Licentie",
"light": "Licht", "light": "Licht",
"loadingFiles": "Bestanden laden...", "loadingFiles": "Bestanden laden...",
@@ -181,6 +182,7 @@
"noClient": "Geen client", "noClient": "Geen client",
"noInterface": "Geen interface", "noInterface": "Geen interface",
"noLineChart": "lijndiagrammen gebruiken", "noLineChart": "lijndiagrammen gebruiken",
"noLineChartForCpu": "Gebruik geen lijndiagrammen voor CPU",
"noNotiPerm": "Geen meldingsmachtigingen, mogelijk geen voortgangsindicatie bij het downloaden van app-updates.", "noNotiPerm": "Geen meldingsmachtigingen, mogelijk geen voortgangsindicatie bij het downloaden van app-updates.",
"noOptions": "Geen opties", "noOptions": "Geen opties",
"noPrivateKeyTip": "De privésleutel bestaat niet, deze is mogelijk verwijderd of er is een configuratiefout.", "noPrivateKeyTip": "De privésleutel bestaat niet, deze is mogelijk verwijderd of er is een configuratiefout.",
@@ -203,11 +205,11 @@
"open": "Openen", "open": "Openen",
"openLastPath": "Open het laatste pad", "openLastPath": "Open het laatste pad",
"openLastPathTip": "Verschillende servers hebben verschillende logs, en de log is het pad naar de uitgang", "openLastPathTip": "Verschillende servers hebben verschillende logs, en de log is het pad naar de uitgang",
"parseContainerStats": "Parseer de containerbezettingstatus",
"parseContainerStatsTip": "Het parsen van de bezettingsstatus van Docker is relatief langzaam.", "parseContainerStatsTip": "Het parsen van de bezettingsstatus van Docker is relatief langzaam.",
"paste": "Plakken", "paste": "Plakken",
"path": "Pad", "path": "Pad",
"percentOfSize": "{percent}% van {size}", "percentOfSize": "{percent}% van {size}",
"permission": "Machtigingen",
"pickFile": "Bestand kiezen", "pickFile": "Bestand kiezen",
"pingAvg": "Gem:", "pingAvg": "Gem:",
"pingInputIP": "Voer een doel-IP / domein in.", "pingInputIP": "Voer een doel-IP / domein in.",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Onbekende status", "serverTabUnkown": "Onbekende status",
"setting": "Instellingen", "setting": "Instellingen",
"sftpDlPrepare": "Voorbereiden om verbinding te maken...", "sftpDlPrepare": "Voorbereiden om verbinding te maken...",
"sftpEditorTip": "Indien leeg, gebruik de ingebouwde bestandseditor van de app. Indien een waarde aanwezig is, gebruik de editor van de externe server, bijvoorbeeld `vim` (aanbevolen om automatisch te detecteren volgens `EDITOR`).",
"sftpRmrDirSummary": "Gebruik `rm -r` om een map te verwijderen in SFTP.", "sftpRmrDirSummary": "Gebruik `rm -r` om een map te verwijderen in SFTP.",
"sftpSSHConnected": "SFTP Verbonden", "sftpSSHConnected": "SFTP Verbonden",
"sftpShowFoldersFirst": "Mappen eerst weergeven", "sftpShowFoldersFirst": "Mappen eerst weergeven",
@@ -276,6 +279,7 @@
"sshTip": "Deze functie bevindt zich momenteel in de experimentele fase.\n\nMeld alstublieft bugs op {url} of sluit je aan bij onze ontwikkeling.", "sshTip": "Deze functie bevindt zich momenteel in de experimentele fase.\n\nMeld alstublieft bugs op {url} of sluit je aan bij onze ontwikkeling.",
"sshVirtualKeyAutoOff": "Automatisch schakelen van virtuele toetsen", "sshVirtualKeyAutoOff": "Automatisch schakelen van virtuele toetsen",
"start": "Starten", "start": "Starten",
"stat": "Statistieken",
"stats": "Statistieken", "stats": "Statistieken",
"stop": "Stoppen", "stop": "Stoppen",
"stopped": "Gestopt", "stopped": "Gestopt",
@@ -302,7 +306,7 @@
"total": "Totaal", "total": "Totaal",
"traffic": "Verkeer", "traffic": "Verkeer",
"trySudo": "Probeer sudo te gebruiken", "trySudo": "Probeer sudo te gebruiken",
"ttl": "ttl", "ttl": "TTL",
"unknown": "Onbekend", "unknown": "Onbekend",
"unknownError": "Onbekende fout", "unknownError": "Onbekende fout",
"unkownConvertMode": "Onbekende conversiemodus", "unkownConvertMode": "Onbekende conversiemodus",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Zal onmiddellijk van kracht worden", "willTakEeffectImmediately": "Zal onmiddellijk van kracht worden",
"wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.", "wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.",
"write": "Schrijven", "write": "Schrijven",
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat." "writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.",
"writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar ~/.config/server_box om de systeemstatus te monitoren. U kunt de inhoud van het script controleren."
} }

View File

@@ -11,7 +11,6 @@
"addr": "Endereço", "addr": "Endereço",
"all": "Todos", "all": "Todos",
"alreadyLastDir": "Já é o diretório mais alto", "alreadyLastDir": "Já é o diretório mais alto",
"alterUrl": "URL alternativa",
"askContinue": "{msg}, continuar?", "askContinue": "{msg}, continuar?",
"attention": "Atenção", "attention": "Atenção",
"authFailTip": "Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.", "authFailTip": "Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.",
@@ -26,7 +25,6 @@
"backupTip": "Os dados exportados são criptografados de forma simples, por favor, guarde-os com segurança.", "backupTip": "Os dados exportados são criptografados de forma simples, por favor, guarde-os com segurança.",
"backupVersionNotMatch": "Versão de backup não compatível, não é possível restaurar", "backupVersionNotMatch": "Versão de backup não compatível, não é possível restaurar",
"battery": "Bateria", "battery": "Bateria",
"beforeConnect": "O ServerBox escreverá e executará um script em `~/.config/server_box` após a conexão. Para mais detalhes técnicos, por favor visite [Github]({url}).",
"bgRun": "Execução em segundo plano", "bgRun": "Execução em segundo plano",
"bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.", "bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.",
"bioAuth": "Autenticação biométrica", "bioAuth": "Autenticação biométrica",
@@ -69,7 +67,6 @@
"decode": "Decodificar", "decode": "Decodificar",
"decompress": "Descomprimir", "decompress": "Descomprimir",
"delete": "Excluir", "delete": "Excluir",
"deleteScripts": "Excluir scripts do servidor simultaneamente",
"deleteServers": "Excluir servidores em lote", "deleteServers": "Excluir servidores em lote",
"deviceName": "Nome do dispositivo", "deviceName": "Nome do dispositivo",
"dirEmpty": "Certifique-se de que a pasta está vazia", "dirEmpty": "Certifique-se de que a pasta está vazia",
@@ -95,13 +92,15 @@
"editor": "Editor", "editor": "Editor",
"editorHighlightTip": "O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.", "editorHighlightTip": "O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.",
"encode": "Codificar", "encode": "Codificar",
"envVars": "Variável de ambiente",
"error": "Erro", "error": "Erro",
"exampleName": "Exemplo de nome", "exampleName": "Exemplo de nome",
"experimentalFeature": "Recurso experimental", "experimentalFeature": "Recurso experimental",
"export": "Exportar", "export": "Exportar",
"extraArgs": "Argumentos extras", "extraArgs": "Argumentos extras",
"failed": "Falhou", "failed": "Falhou",
"fdroidReleaseTip": "Se você baixou este aplicativo do Fdroid, é recomendado desativar esta opção.", "fallbackSshDest": "Destino SSH de fallback",
"fdroidReleaseTip": "Se você baixou este aplicativo do F-Droid, é recomendado desativar esta opção.",
"feedback": "Feedback", "feedback": "Feedback",
"feedbackOnGithub": "Se você tem qualquer problema, por favor, dê feedback no GitHub", "feedbackOnGithub": "Se você tem qualquer problema, por favor, dê feedback no GitHub",
"fieldMustNotEmpty": "Estes campos não podem estar vazios.", "fieldMustNotEmpty": "Estes campos não podem estar vazios.",
@@ -155,6 +154,8 @@
"languageName": "Português", "languageName": "Português",
"lastTry": "Última tentativa", "lastTry": "Última tentativa",
"launchPage": "Página de lançamento", "launchPage": "Página de lançamento",
"letterCache": "Cache de letras",
"letterCacheTip": "Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.",
"license": "Licença de código aberto", "license": "Licença de código aberto",
"light": "Claro", "light": "Claro",
"loadingFiles": "Carregando diretórios...", "loadingFiles": "Carregando diretórios...",
@@ -181,6 +182,7 @@
"noClient": "Sem conexão SSH", "noClient": "Sem conexão SSH",
"noInterface": "Sem interface disponível", "noInterface": "Sem interface disponível",
"noLineChart": "Não usar gráficos de linha", "noLineChart": "Não usar gráficos de linha",
"noLineChartForCpu": "Não utilizar gráficos de linhas para a CPU",
"noNotiPerm": "Sem permissão de notificação, possivelmente sem indicação de progresso ao baixar atualizações de aplicativos.", "noNotiPerm": "Sem permissão de notificação, possivelmente sem indicação de progresso ao baixar atualizações de aplicativos.",
"noOptions": "Sem opções", "noOptions": "Sem opções",
"noPrivateKeyTip": "A chave privada não existe, pode ter sido deletada ou há um erro de configuração.", "noPrivateKeyTip": "A chave privada não existe, pode ter sido deletada ou há um erro de configuração.",
@@ -203,11 +205,11 @@
"open": "Abrir", "open": "Abrir",
"openLastPath": "Abrir o último caminho", "openLastPath": "Abrir o último caminho",
"openLastPathTip": "Registros diferentes para servidores diferentes, e registra o caminho ao sair", "openLastPathTip": "Registros diferentes para servidores diferentes, e registra o caminho ao sair",
"parseContainerStats": "Analisar status de uso do contêiner",
"parseContainerStatsTip": "Análise de status do Docker pode ser lenta", "parseContainerStatsTip": "Análise de status do Docker pode ser lenta",
"paste": "Colar", "paste": "Colar",
"path": "Caminho", "path": "Caminho",
"percentOfSize": "{percent}% de {size}", "percentOfSize": "{percent}% de {size}",
"permission": "Permissões",
"pickFile": "Escolher arquivo", "pickFile": "Escolher arquivo",
"pingAvg": "Média:", "pingAvg": "Média:",
"pingInputIP": "Por favor, insira o IP ou domínio alvo", "pingInputIP": "Por favor, insira o IP ou domínio alvo",
@@ -262,6 +264,7 @@
"serverTabUnkown": "Estado desconhecido", "serverTabUnkown": "Estado desconhecido",
"setting": "Configurações", "setting": "Configurações",
"sftpDlPrepare": "Preparando para conectar ao servidor...", "sftpDlPrepare": "Preparando para conectar ao servidor...",
"sftpEditorTip": "Se vazio, use o editor de arquivos integrado do aplicativo. Se houver um valor, use o editor do servidor remoto, por exemplo, `vim` (recomendado detectar automaticamente de acordo com `EDITOR`).",
"sftpRmrDirSummary": "Usar `rm -r` em SFTP para excluir pastas", "sftpRmrDirSummary": "Usar `rm -r` em SFTP para excluir pastas",
"sftpSSHConnected": "SFTP conectado...", "sftpSSHConnected": "SFTP conectado...",
"sftpShowFoldersFirst": "Mostrar pastas primeiro", "sftpShowFoldersFirst": "Mostrar pastas primeiro",
@@ -276,6 +279,7 @@
"sshTip": "Esta funcionalidade está em fase de teste.\n\nPor favor, reporte problemas em {url} ou junte-se a nós no desenvolvimento.", "sshTip": "Esta funcionalidade está em fase de teste.\n\nPor favor, reporte problemas em {url} ou junte-se a nós no desenvolvimento.",
"sshVirtualKeyAutoOff": "Desativação automática das teclas virtuais", "sshVirtualKeyAutoOff": "Desativação automática das teclas virtuais",
"start": "Iniciar", "start": "Iniciar",
"stat": "Estatísticas",
"stats": "Estatísticas", "stats": "Estatísticas",
"stop": "Parar", "stop": "Parar",
"stopped": "Parado", "stopped": "Parado",
@@ -302,7 +306,7 @@
"total": "Total", "total": "Total",
"traffic": "Tráfego", "traffic": "Tráfego",
"trySudo": "Tentar usar sudo", "trySudo": "Tentar usar sudo",
"ttl": "Tempo de vida do cache", "ttl": "TTL",
"unknown": "Desconhecido", "unknown": "Desconhecido",
"unknownError": "Erro desconhecido", "unknownError": "Erro desconhecido",
"unkownConvertMode": "Modo de conversão desconhecido", "unkownConvertMode": "Modo de conversão desconhecido",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "As alterações serão aplicadas imediatamente", "willTakEeffectImmediately": "As alterações serão aplicadas imediatamente",
"wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.", "wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.",
"write": "Escrita", "write": "Escrita",
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe." "writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.",
"writeScriptTip": "Após conectar ao servidor, um script será escrito em ~/.config/server_box para monitorar o status do sistema. Você pode revisar o conteúdo do script."
} }

View File

@@ -11,7 +11,6 @@
"addr": "Адрес", "addr": "Адрес",
"all": "все", "all": "все",
"alreadyLastDir": "Уже в корневом каталоге", "alreadyLastDir": "Уже в корневом каталоге",
"alterUrl": "альтернативная ссылка",
"askContinue": "{msg}, продолжить?", "askContinue": "{msg}, продолжить?",
"attention": "внимание", "attention": "внимание",
"authFailTip": "Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.", "authFailTip": "Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.",
@@ -26,7 +25,6 @@
"backupTip": "Экспортированные данные зашифрованы простым способом, пожалуйста, храните их в безопасности.", "backupTip": "Экспортированные данные зашифрованы простым способом, пожалуйста, храните их в безопасности.",
"backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно", "backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно",
"battery": "батарея", "battery": "батарея",
"beforeConnect": "ServerBox запишет и выполнит скрипт в `~/.config/server_box` после подключения. Для получения дополнительных технических подробностей, пожалуйста, посетите [Github]({url}).",
"bgRun": "работа в фоновом режиме", "bgRun": "работа в фоновом режиме",
"bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените стратегию энергосбережения на «Без ограничений».", "bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените стратегию энергосбережения на «Без ограничений».",
"bioAuth": "биометрическая аутентификация", "bioAuth": "биометрическая аутентификация",
@@ -69,7 +67,6 @@
"decode": "декодировать", "decode": "декодировать",
"decompress": "разархивировать", "decompress": "разархивировать",
"delete": "удалить", "delete": "удалить",
"deleteScripts": "удалить скрипты с сервера",
"deleteServers": "удалить серверы пакетно", "deleteServers": "удалить серверы пакетно",
"deviceName": "Название устройства", "deviceName": "Название устройства",
"dirEmpty": "Пожалуйста, убедитесь, что папка пуста", "dirEmpty": "Пожалуйста, убедитесь, что папка пуста",
@@ -95,13 +92,15 @@
"editor": "редактор", "editor": "редактор",
"editorHighlightTip": "Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.", "editorHighlightTip": "Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.",
"encode": "кодировать", "encode": "кодировать",
"envVars": "Переменная окружения",
"error": "ошибка", "error": "ошибка",
"exampleName": "пример имени", "exampleName": "пример имени",
"experimentalFeature": "экспериментальная функция", "experimentalFeature": "экспериментальная функция",
"export": "экспорт", "export": "экспорт",
"extraArgs": "дополнительные аргументы", "extraArgs": "дополнительные аргументы",
"failed": "неудача", "failed": "неудача",
"fdroidReleaseTip": "Если вы скачали это приложение с Fdroid, рекомендуется отключить эту опцию.", "fallbackSshDest": "Резервное место назначения SSH",
"fdroidReleaseTip": "Если вы скачали это приложение с F-Droid, рекомендуется отключить эту опцию.",
"feedback": "обратная связь", "feedback": "обратная связь",
"feedbackOnGithub": "Если у вас есть какие-либо вопросы, пожалуйста, отправьте отзыв на GitHub", "feedbackOnGithub": "Если у вас есть какие-либо вопросы, пожалуйста, отправьте отзыв на GitHub",
"fieldMustNotEmpty": "Эти поля не могут быть пустыми.", "fieldMustNotEmpty": "Эти поля не могут быть пустыми.",
@@ -155,6 +154,8 @@
"languageName": "Русский", "languageName": "Русский",
"lastTry": "последняя попытка", "lastTry": "последняя попытка",
"launchPage": "стартовая страница", "launchPage": "стартовая страница",
"letterCache": "Кэширование букв",
"letterCacheTip": "Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.",
"license": "лицензия", "license": "лицензия",
"light": "светлая", "light": "светлая",
"loadingFiles": "Загрузка файлов...", "loadingFiles": "Загрузка файлов...",
@@ -181,6 +182,7 @@
"noClient": "нет SSH соединения", "noClient": "нет SSH соединения",
"noInterface": "нет доступных интерфейсов", "noInterface": "нет доступных интерфейсов",
"noLineChart": "Не использовать линейные графики", "noLineChart": "Не использовать линейные графики",
"noLineChartForCpu": "Не используйте линейные графики для ЦП",
"noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.", "noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.",
"noOptions": "нет доступных опций", "noOptions": "нет доступных опций",
"noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.", "noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.",
@@ -203,11 +205,11 @@
"open": "открыть", "open": "открыть",
"openLastPath": "открыть последний путь", "openLastPath": "открыть последний путь",
"openLastPathTip": "Для разных серверов будут сохранены разные записи, записывается путь при выходе", "openLastPathTip": "Для разных серверов будут сохранены разные записи, записывается путь при выходе",
"parseContainerStats": "анализ статуса использования контейнера",
"parseContainerStatsTip": "Анализ статуса использования Docker может быть медленным", "parseContainerStatsTip": "Анализ статуса использования Docker может быть медленным",
"paste": "вставить", "paste": "вставить",
"path": "путь", "path": "путь",
"percentOfSize": "{percent}% от {size}", "percentOfSize": "{percent}% от {size}",
"permission": "Разрешения",
"pickFile": "выбрать файл", "pickFile": "выбрать файл",
"pingAvg": "Среднее:", "pingAvg": "Среднее:",
"pingInputIP": "Пожалуйста, введите целевой IP или доменное имя", "pingInputIP": "Пожалуйста, введите целевой IP или доменное имя",
@@ -262,6 +264,7 @@
"serverTabUnkown": "неизвестно", "serverTabUnkown": "неизвестно",
"setting": "настройки", "setting": "настройки",
"sftpDlPrepare": "Подготовка к подключению к серверу...", "sftpDlPrepare": "Подготовка к подключению к серверу...",
"sftpEditorTip": "Если пусто, используйте встроенный редактор файлов приложения. Если значение указано, используйте редактор удаленного сервера, например, `vim` (рекомендуется автоматически определять согласно `EDITOR`).",
"sftpRmrDirSummary": "Использовать `rm -r` в SFTP для удаления папок", "sftpRmrDirSummary": "Использовать `rm -r` в SFTP для удаления папок",
"sftpSSHConnected": "SFTP подключен...", "sftpSSHConnected": "SFTP подключен...",
"sftpShowFoldersFirst": "показывать папки в начале", "sftpShowFoldersFirst": "показывать папки в начале",
@@ -276,6 +279,7 @@
"sshTip": "Эта функция находится в стадии тестирования.\n\nПожалуйста, отправляйте отчеты о проблемах на {url} или присоединяйтесь к нашей разработке.", "sshTip": "Эта функция находится в стадии тестирования.\n\nПожалуйста, отправляйте отчеты о проблемах на {url} или присоединяйтесь к нашей разработке.",
"sshVirtualKeyAutoOff": "автоматическое отключение виртуальных клавиш", "sshVirtualKeyAutoOff": "автоматическое отключение виртуальных клавиш",
"start": "старт", "start": "старт",
"stat": "Статистика",
"stats": "статистика", "stats": "статистика",
"stop": "остановить", "stop": "остановить",
"stopped": "остановлено", "stopped": "остановлено",
@@ -302,7 +306,7 @@
"total": "всего", "total": "всего",
"traffic": "трафик", "traffic": "трафик",
"trySudo": "попробовать использовать sudo", "trySudo": "попробовать использовать sudo",
"ttl": "время жизни кеша", "ttl": "TTL",
"unknown": "неизвестно", "unknown": "неизвестно",
"unknownError": "неизвестная ошибка", "unknownError": "неизвестная ошибка",
"unkownConvertMode": "неизвестный режим конвертации", "unkownConvertMode": "неизвестный режим конвертации",
@@ -338,5 +342,6 @@
"willTakEeffectImmediately": "Изменения вступят в силу немедленно", "willTakEeffectImmediately": "Изменения вступят в силу немедленно",
"wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.", "wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.",
"write": "запись", "write": "запись",
"writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует." "writeScriptFailTip": "Запись в скрипт не удалась, возможно, из-за отсутствия прав или директории не существует.",
"writeScriptTip": "После подключения к серверу скрипт будет записан в ~/.config/server_box для мониторинга состояния системы. Вы можете проверить содержимое скрипта."
} }

View File

@@ -5,13 +5,12 @@
"acceptBeta": "接受测试版更新推送", "acceptBeta": "接受测试版更新推送",
"add": "新增", "add": "新增",
"addAServer": "添加服务器", "addAServer": "添加服务器",
"addPrivateKey": "添加一个私钥", "addPrivateKey": "添加私钥",
"addSystemPrivateKeyTip": "当前没有任何私钥,是否添加系统自带的(~/.ssh/id_rsa", "addSystemPrivateKeyTip": "当前没有任何私钥,是否添加系统自带的(~/.ssh/id_rsa",
"added2List": "已添加至任务列表", "added2List": "已添加至任务列表",
"addr": "地址", "addr": "地址",
"all": "所有", "all": "所有",
"alreadyLastDir": "已经是最上层目录了", "alreadyLastDir": "已经是最上层目录了",
"alterUrl": "备选链接",
"askContinue": "{msg},继续吗?", "askContinue": "{msg},继续吗?",
"attention": "注意", "attention": "注意",
"authFailTip": "认证失败,请检查密码/密钥/主机/用户等是否错误", "authFailTip": "认证失败,请检查密码/密钥/主机/用户等是否错误",
@@ -26,9 +25,8 @@
"backupTip": "导出的数据仅进行了简单加密,请妥善保管。", "backupTip": "导出的数据仅进行了简单加密,请妥善保管。",
"backupVersionNotMatch": "备份版本不匹配,无法恢复", "backupVersionNotMatch": "备份版本不匹配,无法恢复",
"battery": "电池", "battery": "电池",
"beforeConnect": "ServerBox 将在连接后,在 `~/.config/server_box` 写入脚本并执行,更多的技术细节请访问 [Github]({url})。",
"bgRun": "后台运行", "bgRun": "后台运行",
"bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI 请修改省电策略为“无限制”。", "bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请修改省电策略为“无限制”。",
"bioAuth": "生物认证", "bioAuth": "生物认证",
"browser": "浏览器", "browser": "浏览器",
"bulkImportServers": "批量导入服务器", "bulkImportServers": "批量导入服务器",
@@ -42,21 +40,21 @@
"clipboard": "剪切板", "clipboard": "剪切板",
"close": "关闭", "close": "关闭",
"cmd": "命令", "cmd": "命令",
"cnKeyboardComp": "中国Android兼容性", "cnKeyboardComp": "中国 Android 兼容性",
"cnKeyboardCompTip": "如果终端弹出安全键盘,可以开启", "cnKeyboardCompTip": "如果终端弹出安全键盘,可以开启",
"collapseUI": "折叠", "collapseUI": "折叠",
"collapseUITip": "是否默认折叠UI中存在的长列表", "collapseUITip": "是否默认折叠 UI 中的长列表",
"conn": "连接", "conn": "连接",
"connected": "已连接", "connected": "已连接",
"container": "容器", "container": "容器",
"containerName": "容器名", "containerName": "容器名",
"containerStatus": "容器状态", "containerStatus": "容器状态",
"containerTrySudoTip": "例如在应用内将用户设置为aaa但是Docker安装在root用户下这时就需要启用此选项", "containerTrySudoTip": "例如:在应用内将用户设置为 aaa但是 Docker 安装在root用户下这时就需要启用此选项",
"content": "内容", "content": "内容",
"convert": "转换", "convert": "转换",
"copy": "复制", "copy": "复制",
"copyPath": "复制路径", "copyPath": "复制路径",
"cpuViewAsProgressTip": "以进度条样式显示每个CPU的使用率旧版样式", "cpuViewAsProgressTip": "以进度条样式显示每个 CPU 的使用率(旧版样式)",
"createFile": "创建文件", "createFile": "创建文件",
"createFolder": "创建文件夹", "createFolder": "创建文件夹",
"cursorType": "光标类型", "cursorType": "光标类型",
@@ -69,24 +67,23 @@
"decode": "解码", "decode": "解码",
"decompress": "解压缩", "decompress": "解压缩",
"delete": "删除", "delete": "删除",
"deleteScripts": "同时删除服务器脚本",
"deleteServers": "批量删除服务器", "deleteServers": "批量删除服务器",
"deviceName": "设备名", "deviceName": "设备名",
"dirEmpty": "请确保文件夹为空", "dirEmpty": "请确保文件夹为空",
"disabled": "已禁用", "disabled": "已禁用",
"disconnected": "连接断开", "disconnected": "连接断开",
"disk": "盘", "disk": "盘",
"diskIgnorePath": "忽略的磁盘路径", "diskIgnorePath": "忽略的磁盘路径",
"displayCpuIndex": "显示CPU索引", "displayCpuIndex": "显示 CPU 索引",
"displayName": "显示名称", "displayName": "显示名称",
"dl2Local": "下载 {fileName} 到本地?", "dl2Local": "下载 {fileName} 到本地?",
"doc": "文档", "doc": "文档",
"dockerEditHost": "编辑 DOCKER_HOST", "dockerEditHost": "编辑 DOCKER_HOST",
"dockerEmptyRunningItems": "没有正在运行的容器。\n这可能是因为\n- Docker 安装用户与 App 内配置的用户名不同\n- 环境变量 DOCKER_HOST 没有被正确读取。可以通过在终端内运行 `echo $DOCKER_HOST` 来获取。", "dockerEmptyRunningItems": "没有正在运行的容器。\n这可能是因为\n- Docker 安装用户与 App 内配置的用户名不同\n- 环境变量 DOCKER_HOST 没有被正确读取。可以通过在终端内运行 `echo $DOCKER_HOST` 来获取。",
"dockerImagesFmt": "共 {count} 个镜像", "dockerImagesFmt": "共 {count} 个镜像",
"dockerNotInstalled": "Docker 未安装", "dockerNotInstalled": "Docker 未安装",
"dockerStatusRunningAndStoppedFmt": "{runningCount}个正在运行, {stoppedCount}个已停止", "dockerStatusRunningAndStoppedFmt": "{runningCount} 个正在运行, {stoppedCount} 个已停止",
"dockerStatusRunningFmt": "{count}个容器正在运行", "dockerStatusRunningFmt": "{count} 个容器正在运行",
"doubleColumnMode": "双列模式", "doubleColumnMode": "双列模式",
"doubleColumnTip": "此选项仅开启功能,实际是否能开启还取决于设备的宽度", "doubleColumnTip": "此选项仅开启功能,实际是否能开启还取决于设备的宽度",
"download": "下载", "download": "下载",
@@ -95,13 +92,15 @@
"editor": "编辑器", "editor": "编辑器",
"editorHighlightTip": "目前的代码高亮性能较为糟糕,可以选择关闭以改善。", "editorHighlightTip": "目前的代码高亮性能较为糟糕,可以选择关闭以改善。",
"encode": "编码", "encode": "编码",
"envVars": "环境变量",
"error": "错误", "error": "错误",
"exampleName": "名称示例", "exampleName": "名称示例",
"experimentalFeature": "实验性功能", "experimentalFeature": "实验性功能",
"export": "导出", "export": "导出",
"extraArgs": "额外参数", "extraArgs": "额外参数",
"failed": "失败", "failed": "失败",
"fdroidReleaseTip": "如果你是从 Fdroid 下载的本应用,推荐关闭此选项", "fallbackSshDest": "备选 SSH 目标",
"fdroidReleaseTip": "如果你是从 F-Droid 下载的本应用,推荐关闭此选项",
"feedback": "反馈", "feedback": "反馈",
"feedbackOnGithub": "如果你有任何问题请在GitHub反馈", "feedbackOnGithub": "如果你有任何问题请在GitHub反馈",
"fieldMustNotEmpty": "这些输入框不能为空。", "fieldMustNotEmpty": "这些输入框不能为空。",
@@ -118,9 +117,9 @@
"fullScreen": "全屏模式", "fullScreen": "全屏模式",
"fullScreenJitter": "全屏模式抖动", "fullScreenJitter": "全屏模式抖动",
"fullScreenJitterHelp": "防止烧屏", "fullScreenJitterHelp": "防止烧屏",
"fullScreenTip": "当设备旋转为横屏时是否开启全屏模式。此选项仅作用于服务器Tab页。", "fullScreenTip": "当设备旋转为横屏时,是否开启全屏模式。此选项仅作用于服务器 Tab 页。",
"getPushTokenFailed": "未能获取到推送token", "getPushTokenFailed": "未能获取到推送 token",
"gettingToken": "正在获取Token...", "gettingToken": "正在获取 Token...",
"goBackQ": "返回?", "goBackQ": "返回?",
"goto": "前往", "goto": "前往",
"hideTitleBar": "隐藏标题栏", "hideTitleBar": "隐藏标题栏",
@@ -130,12 +129,12 @@
"host": "主机", "host": "主机",
"hour": "小时", "hour": "小时",
"httpFailedWithCode": "请求失败, 状态码: {code}", "httpFailedWithCode": "请求失败, 状态码: {code}",
"icloudSynced": "iCloud已同步某些设置可能需要重启才能生效。", "icloudSynced": "iCloud 已同步,某些设置可能需要重启才能生效。",
"ignoreCert": "忽略证书", "ignoreCert": "忽略证书",
"image": "镜像", "image": "镜像",
"imagesList": "镜像列表", "imagesList": "镜像列表",
"import": "导入", "import": "导入",
"inAppUpdate": "在App内更新否则使用浏览器下载", "inAppUpdate": "在 App 内更新?否则使用浏览器下载",
"init": "初始化", "init": "初始化",
"inner": "内置", "inner": "内置",
"inputDomainHere": "在这里输入域名", "inputDomainHere": "在这里输入域名",
@@ -144,7 +143,7 @@
"invalid": "无效", "invalid": "无效",
"invalidJson": "无效的 JSON", "invalidJson": "无效的 JSON",
"invalidVersion": "不支持的版本", "invalidVersion": "不支持的版本",
"invalidVersionHelp": "请确保正确安装了docker或者使用的非自编译版本。如果没有以上问题请在 {url} 提交问题。", "invalidVersionHelp": "请确保正确安装了 docker或者使用的非自编译版本。如果没有以上问题请在 {url} 提交问题。",
"isBusy": "当前正忙", "isBusy": "当前正忙",
"jumpServer": "跳板服务器", "jumpServer": "跳板服务器",
"keepForeground": "请保持应用处于前台!", "keepForeground": "请保持应用处于前台!",
@@ -155,7 +154,9 @@
"languageName": "简体中文", "languageName": "简体中文",
"lastTry": "最后尝试", "lastTry": "最后尝试",
"launchPage": "启动页", "launchPage": "启动页",
"license": "开源证书", "letterCache": "输入法字符缓存",
"letterCacheTip": "推荐关闭,但是关闭后无法输入 CJK 等文字",
"license": "开源许可证",
"light": "亮", "light": "亮",
"loadingFiles": "正在加载目录。。。", "loadingFiles": "正在加载目录。。。",
"location": "位置", "location": "位置",
@@ -178,10 +179,11 @@
"net": "网络", "net": "网络",
"netViewType": "网络视图类型", "netViewType": "网络视图类型",
"newContainer": "新建容器", "newContainer": "新建容器",
"noClient": "没有SSH连接", "noClient": "没有 SSH 连接",
"noInterface": "没有可用的接口", "noInterface": "没有可用的接口",
"noLineChart": "不使用折线图", "noLineChart": "不使用折线图",
"noNotiPerm": "无通知权限可能下载App更新时无进度提示。", "noLineChartForCpu": "CPU 不使用折线图",
"noNotiPerm": "无通知权限,可能下载 App 更新时无进度提示。",
"noOptions": "无可选项", "noOptions": "无可选项",
"noPrivateKeyTip": "私钥不存在,可能已被删除/配置错误", "noPrivateKeyTip": "私钥不存在,可能已被删除/配置错误",
"noPromptAgain": "不再提示", "noPromptAgain": "不再提示",
@@ -195,23 +197,23 @@
"notAvailable": "不可用", "notAvailable": "不可用",
"notSelected": "未选择", "notSelected": "未选择",
"note": "备注", "note": "备注",
"nullToken": "无Token", "nullToken": "无 Token",
"ok": "好", "ok": "好",
"onServerDetailPage": "在服务器详情页", "onServerDetailPage": "在服务器详情页",
"onlyOneLine": "仅显示为一行(可滚动)", "onlyOneLine": "仅显示为一行(可滚动)",
"onlyWhenCoreBiggerThan8": "仅当核心数>8时生效", "onlyWhenCoreBiggerThan8": "仅当核心数大于 8 时生效",
"open": "打开", "open": "打开",
"openLastPath": "打开上次的路径", "openLastPath": "打开上次的路径",
"openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径", "openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径",
"parseContainerStats": "解析容器占用状态", "parseContainerStatsTip": "Docker 解析占用状态较为缓慢",
"parseContainerStatsTip": "Docker解析占用状态较为缓慢",
"paste": "粘贴", "paste": "粘贴",
"path": "路径", "path": "路径",
"percentOfSize": "{size} 的 {percent}%", "percentOfSize": "{size} 的 {percent}%",
"permission": "权限",
"pickFile": "选择文件", "pickFile": "选择文件",
"pingAvg": "平均:", "pingAvg": "平均:",
"pingInputIP": "请输入目标IP或域名", "pingInputIP": "请输入目标IP或域名",
"pingNoServer": "没有服务器可用于Ping\n请在服务器tab添加服务器后再试", "pingNoServer": "没有服务器可用于 Ping\n请在服务器 tab 添加服务器后再试",
"pkg": "包管理", "pkg": "包管理",
"pkgUpgradeTip": "请在更新前备份系统。", "pkgUpgradeTip": "请在更新前备份系统。",
"platformNotSupportUpdate": "当前平台不支持更新,请编译最新源码后手动安装", "platformNotSupportUpdate": "当前平台不支持更新,请编译最新源码后手动安装",
@@ -224,9 +226,9 @@
"privateKey": "私钥", "privateKey": "私钥",
"process": "进程", "process": "进程",
"pushToken": "消息推送 Token", "pushToken": "消息推送 Token",
"pveIgnoreCertTip": "不推荐开启注意安全隐患如果你使用的PVE默认证书需要开启该选项", "pveIgnoreCertTip": "不推荐开启,注意安全隐患!如果你使用的 PVE 默认证书,需要开启该选项",
"pveLoginFailed": "登录失败。无法使用服务器配置内的用户/密码以Linux PAM方式登录。", "pveLoginFailed": "登录失败。无法使用服务器配置内的用户/密码,以 Linux PAM 方式登录。",
"pveVersionLow": "当前该功能处于测试阶段仅在PVE 8+上测试过,请谨慎使用", "pveVersionLow": "当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用",
"pwd": "密码", "pwd": "密码",
"read": "读", "read": "读",
"reboot": "重启", "reboot": "重启",
@@ -239,7 +241,7 @@
"reportBugsOnGithubIssue": "请到 {url} 提交问题", "reportBugsOnGithubIssue": "请到 {url} 提交问题",
"restart": "重启", "restart": "重启",
"restore": "恢复", "restore": "恢复",
"restoreSuccess": "恢复成功需要重启App来应用更改", "restoreSuccess": "恢复成功,需要重启 App 来应用更改",
"result": "结果", "result": "结果",
"rotateAngel": "旋转角度", "rotateAngel": "旋转角度",
"route": "路由", "route": "路由",
@@ -262,6 +264,7 @@
"serverTabUnkown": "未知状态", "serverTabUnkown": "未知状态",
"setting": "设置", "setting": "设置",
"sftpDlPrepare": "准备连接至服务器...", "sftpDlPrepare": "准备连接至服务器...",
"sftpEditorTip": "如果为空, 使用App内置的文件编辑器. 如果有值, 这是用远程服务器的编辑器, 例如 `vim` (建议根据 `EDITOR` 自动获取).",
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹", "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹",
"sftpSSHConnected": "SFTP 已连接...", "sftpSSHConnected": "SFTP 已连接...",
"sftpShowFoldersFirst": "文件夹显示在前", "sftpShowFoldersFirst": "文件夹显示在前",
@@ -272,10 +275,11 @@
"softWrap": "自动换行", "softWrap": "自动换行",
"speed": "速度", "speed": "速度",
"spentTime": "耗时: {time}", "spentTime": "耗时: {time}",
"sshTermHelp": "在终端可滚动时,横向拖动可以选中文字。点击键盘按钮可以开启/关闭键盘。文件图标会打开当前路径SFTP。粘贴板按钮会在有选中文字时复制内容,在未选中并且剪切板有内容时粘贴内容到终端。代码图标会粘贴代码片段到终端并执行。", "sshTermHelp": "在终端可滚动时,横向拖动可以选中文字。点击键盘按钮可以开启/关闭键盘。文件图标会打开当前路径 SFTP。剪切板按钮会在有选中文字时复制内容,在未选中并且剪切板有内容时粘贴内容到终端。代码图标会粘贴代码片段到终端并执行。",
"sshTip": "该功能目前处于测试阶段。\n\n请在 {url} 反馈问题,或者加入我们开发。", "sshTip": "该功能目前处于测试阶段。\n\n请在 {url} 反馈问题,或者加入我们开发。",
"sshVirtualKeyAutoOff": "虚拟按键自动切换", "sshVirtualKeyAutoOff": "虚拟按键自动切换",
"start": "开始", "start": "开始",
"stat": "统计",
"stats": "统计", "stats": "统计",
"stop": "停止", "stop": "停止",
"stopped": "已停止", "stopped": "已停止",
@@ -290,7 +294,7 @@
"system": "系统", "system": "系统",
"tag": "标签", "tag": "标签",
"temperature": "温度", "temperature": "温度",
"termFontSizeTip": "此设置会影响终端大小(宽高)。可以在终端页面缩放来调整当前会话的字体大小", "termFontSizeTip": "此设置会影响终端大小(宽高)。可以在终端页面缩放来调整当前会话的字体大小",
"terminal": "终端", "terminal": "终端",
"test": "测试", "test": "测试",
"textScaler": "字体缩放", "textScaler": "字体缩放",
@@ -301,23 +305,23 @@
"times": "次", "times": "次",
"total": "总共", "total": "总共",
"traffic": "流量", "traffic": "流量",
"trySudo": "尝试使用sudo", "trySudo": "尝试使用 sudo",
"ttl": "缓存时间", "ttl": "TTL",
"unknown": "未知", "unknown": "未知",
"unknownError": "未知错误", "unknownError": "未知错误",
"unkownConvertMode": "未知转换模式", "unkownConvertMode": "未知转换模式",
"update": "更新", "update": "更新",
"updateAll": "更新全部", "updateAll": "更新全部",
"updateIntervalEqual0": "你设置为0服务器状态不会自动刷新。\n且不能计算CPU使用情况。", "updateIntervalEqual0": "你设置为 0服务器状态不会自动刷新。\n且不能计算 CPU 使用情况。",
"updateServerStatusInterval": "服务器状态刷新间隔", "updateServerStatusInterval": "服务器状态刷新间隔",
"updateTip": "新版本: v1.0.{newest}", "updateTip": "新版本: v1.0.{newest}",
"updateTipTooLow": "当前版本过低,请升级至 v1.0.{newest}", "updateTipTooLow": "当前版本过低,请升级至 v1.0.{newest}",
"upload": "上传", "upload": "上传",
"upsideDown": "上下交换", "upsideDown": "上下交换",
"uptime": "启动时长", "uptime": "启动时长",
"urlOrJson": "链接或JSON", "urlOrJson": "链接或 JSON",
"useCdn": "使用CDN", "useCdn": "使用 CDN",
"useCdnTip": "非中国大陆用户推荐使用CDN是否使用", "useCdnTip": "非中国大陆用户推荐使用 CDN是否使用",
"useNoPwd": "将会使用无密码", "useNoPwd": "将会使用无密码",
"usePodmanByDefault": "默认使用 Podman", "usePodmanByDefault": "默认使用 Podman",
"used": "已用", "used": "已用",
@@ -333,10 +337,11 @@
"waitConnection": "请等待连接建立", "waitConnection": "请等待连接建立",
"wakeLock": "保持唤醒", "wakeLock": "保持唤醒",
"watchNotPaired": "没有已配对的 Apple Watch", "watchNotPaired": "没有已配对的 Apple Watch",
"webdavSettingEmpty": "Webdav 设置项为空", "webdavSettingEmpty": "WebDav 设置项为空",
"whenOpenApp": "当打开 App 时", "whenOpenApp": "当打开 App 时",
"willTakEeffectImmediately": "更改将会立即生效", "willTakEeffectImmediately": "更改将会立即生效",
"wolTip": "在配置 WOL 后,每次连接服务器都会先发送一次 WOl 请求", "wolTip": "在配置 WOL 后,每次连接服务器都会先发送一次 WOL 请求",
"write": "写", "write": "写",
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等" "writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
"writeScriptTip": "在连接服务器后,会向 ~/.config/server_box 写入脚本来监测系统状态,你可以审查脚本内容。"
} }

View File

@@ -4,31 +4,29 @@
"aboutThanks": "感謝以下參與的各位。", "aboutThanks": "感謝以下參與的各位。",
"acceptBeta": "接受測試版更新推送", "acceptBeta": "接受測試版更新推送",
"add": "新增", "add": "新增",
"addAServer": "新增服器", "addAServer": "新增服器",
"addPrivateKey": "新增一個私鑰", "addPrivateKey": "新增私鑰",
"addSystemPrivateKeyTip": "當前沒有任何私鑰,是否添加系統自帶的~/.ssh/id_rsa", "addSystemPrivateKeyTip": "當前沒有任何私鑰,是否添加系統自帶的 (~/.ssh/id_rsa)",
"added2List": "已添加至任務列表", "added2List": "已添加至任務列表",
"addr": "址", "addr": "址",
"all": "所有", "all": "所有",
"alreadyLastDir": "已經是最上層目錄了", "alreadyLastDir": "已經是最上層目錄了",
"alterUrl": "備選鏈接",
"askContinue": "{msg},繼續嗎?", "askContinue": "{msg},繼續嗎?",
"attention": "注意", "attention": "注意",
"authFailTip": "認證失敗,請檢查密碼/密鑰/主機/用戶等是否錯誤。", "authFailTip": "認證失敗,請檢查密碼/密鑰/主機/用戶等是否錯誤。",
"authRequired": "需要認證", "authRequired": "需要認證",
"auto": "自動", "auto": "自動",
"autoBackupConflict": "只能同時開啓個自動備份", "autoBackupConflict": "只能同時開啓個自動備份",
"autoCheckUpdate": "自動檢查更新", "autoCheckUpdate": "自動檢查更新",
"autoConnect": "自動連接", "autoConnect": "自動連接",
"autoRun": "自動運行", "autoRun": "自動運行",
"autoUpdateHomeWidget": "自動更新桌面小部件", "autoUpdateHomeWidget": "自動更新桌面小部件",
"backup": "備份", "backup": "備份",
"backupTip": "出的數據僅進行了簡單加密,請妥善保管。", "backupTip": "出的資料僅進行了簡單加密,請妥善保管。",
"backupVersionNotMatch": "備份版本不匹配,無法還原", "backupVersionNotMatch": "備份版本不匹配,無法還原",
"battery": "電池", "battery": "電池",
"beforeConnect": "ServerBox將在連接後在`~/.config/server_box`寫入腳本並執行,更多的技術細節請訪問[Github]({url})。", "bgRun": "後台運行",
"bgRun": "背景運行", "bgRunTip": "此開關只代表程式會嘗試在後台運行,具體能否在後臺運行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池優化”MIUI / HyperOS 請修改省電策略為“無限制”。",
"bgRunTip": "此開關只代表程式會嘗試在背景執行,具體能否背景運行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池優化”MIUI 請修改省電策略為“無限制”。",
"bioAuth": "生物認證", "bioAuth": "生物認證",
"browser": "瀏覽器", "browser": "瀏覽器",
"bulkImportServers": "批量導入伺服器", "bulkImportServers": "批量導入伺服器",
@@ -36,48 +34,47 @@
"canPullRefresh": "可以下拉更新", "canPullRefresh": "可以下拉更新",
"cancel": "取消", "cancel": "取消",
"choose": "選擇", "choose": "選擇",
"chooseFontFile": "選擇字體文件", "chooseFontFile": "選擇字型檔",
"choosePrivateKey": "選擇私鑰", "choosePrivateKey": "選擇私鑰",
"clear": "清除", "clear": "清除",
"clipboard": "剪切板", "clipboard": "剪貼簿",
"close": "關閉", "close": "關閉",
"cmd": "命令", "cmd": "命令",
"cnKeyboardComp": "中國Android兼容性", "cnKeyboardComp": "中國 Android 兼容性",
"cnKeyboardCompTip": "如果終端彈出安全鍵盤,您可以啟用它。", "cnKeyboardCompTip": "如果終端彈出安全鍵盤,您可以啟用它。",
"collapseUI": "折疊", "collapseUI": "折疊",
"collapseUITip": "是否預設折疊UI中存在的長列表", "collapseUITip": "是否預設折疊 UI 中存在的長列表",
"conn": "連接", "conn": "連接",
"connected": "已連接", "connected": "已連接",
"container": "容器", "container": "容器",
"containerName": "容器名稱", "containerName": "容器名稱",
"containerStatus": "容器狀態", "containerStatus": "容器狀態",
"containerTrySudoTip": "例如App内设置用户为aaa但是Docker安装在root用户这时就需要开启此选项", "containerTrySudoTip": "例如App 內設置使用者為 aaa但是 Docker 安裝在 root 使用者,這時就需要開啟此選項",
"content": "內容", "content": "內容",
"convert": "轉換", "convert": "轉換",
"copy": "複製", "copy": "複製",
"copyPath": "複製路徑", "copyPath": "複製路徑",
"cpuViewAsProgressTip": "以進度條樣式顯示每個CPU的使用率舊版樣式", "cpuViewAsProgressTip": "以進度條樣式顯示每個CPU的使用率舊版樣式",
"createFile": "創建文件", "createFile": "創建檔案",
"createFolder": "創建文件夾", "createFolder": "創建資料夾",
"cursorType": "標類型", "cursorType": "標類型",
"customCmd": "自定義命令", "customCmd": "自命令",
"customCmdDocUrl": "https://github.com/lollipopkit/flutter_server_box/wiki/主页#自定义命令", "customCmdDocUrl": "https://github.com/lollipopkit/flutter_server_box/wiki/主页#自定义命令",
"customCmdHint": "\"命令名稱\": \"命令\"", "customCmdHint": "\"命令名稱\": \"命令\"",
"dark": "暗", "dark": "暗",
"day": "", "day": "",
"debug": "調試", "debug": "除錯",
"decode": "解碼", "decode": "解碼",
"decompress": "解壓縮", "decompress": "解壓縮",
"delete": "刪除", "delete": "刪除",
"deleteScripts": "同時刪除服務器腳本", "deleteServers": "批量刪除伺服器",
"deleteServers": "批量刪除服務器",
"deviceName": "設備名", "deviceName": "設備名",
"dirEmpty": "請確保文件夾為空", "dirEmpty": "請確保資料夾為空",
"disabled": "已禁用", "disabled": "已禁用",
"disconnected": "連接斷開", "disconnected": "連接斷開",
"disk": "硬盤", "disk": "磁碟",
"diskIgnorePath": "忽略的磁路徑", "diskIgnorePath": "忽略的磁路徑",
"displayCpuIndex": "顯示CPU索引", "displayCpuIndex": "顯示 CPU 索引",
"displayName": "顯示名稱", "displayName": "顯示名稱",
"dl2Local": "下載 {fileName} 到本地?", "dl2Local": "下載 {fileName} 到本地?",
"doc": "文檔", "doc": "文檔",
@@ -85,8 +82,8 @@
"dockerEmptyRunningItems": "沒有正在運行的容器。\n這可能是因為\n- Docker 安裝使用者與 App 內配置的使用者名稱不同\n- 環境變量 DOCKER_HOST 沒有被正確讀取。你可以通過在終端內運行 `echo $DOCKER_HOST` 來獲取。", "dockerEmptyRunningItems": "沒有正在運行的容器。\n這可能是因為\n- Docker 安裝使用者與 App 內配置的使用者名稱不同\n- 環境變量 DOCKER_HOST 沒有被正確讀取。你可以通過在終端內運行 `echo $DOCKER_HOST` 來獲取。",
"dockerImagesFmt": "共 {count} 個鏡像", "dockerImagesFmt": "共 {count} 個鏡像",
"dockerNotInstalled": "Docker 未安裝", "dockerNotInstalled": "Docker 未安裝",
"dockerStatusRunningAndStoppedFmt": "{runningCount}個正在運行, {stoppedCount}個已停止", "dockerStatusRunningAndStoppedFmt": "{runningCount} 個正在運行, {stoppedCount} 個已停止",
"dockerStatusRunningFmt": "{count}個容器正在運行", "dockerStatusRunningFmt": "{count} 個容器正在運行",
"doubleColumnMode": "雙列模式", "doubleColumnMode": "雙列模式",
"doubleColumnTip": "此選項僅開啟功能,實際是否能開啟還取決於設備的寬度", "doubleColumnTip": "此選項僅開啟功能,實際是否能開啟還取決於設備的寬度",
"download": "下載", "download": "下載",
@@ -95,32 +92,34 @@
"editor": "編輯器", "editor": "編輯器",
"editorHighlightTip": "目前的代碼高亮性能較為糟糕,可以選擇關閉以改善。", "editorHighlightTip": "目前的代碼高亮性能較為糟糕,可以選擇關閉以改善。",
"encode": "編碼", "encode": "編碼",
"envVars": "環境變量",
"error": "錯誤", "error": "錯誤",
"exampleName": "名稱範例", "exampleName": "名稱範例",
"experimentalFeature": "實驗性功能", "experimentalFeature": "實驗性功能",
"export": "出", "export": "出",
"extraArgs": "額外參數", "extraArgs": "額外參數",
"failed": "失敗", "failed": "失敗",
"fdroidReleaseTip": "如果你是從 Fdroid 下載的本應用,推薦關閉此選項", "fallbackSshDest": "備選 SSH 目標",
"fdroidReleaseTip": "如果你是從 F-Droid 下載的本應用,推薦關閉此選項",
"feedback": "反饋", "feedback": "反饋",
"feedbackOnGithub": "如果你有任何問題請在GitHub反饋", "feedbackOnGithub": "如果你有任何問題,請在 GitHub 反饋",
"fieldMustNotEmpty": "這些輸入框不能為空。", "fieldMustNotEmpty": "這些輸入框不能為空。",
"fileNotExist": "{file} 不存在", "fileNotExist": "{file} 不存在",
"fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}", "fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}",
"files": "文件", "files": "文件",
"finished": "已完成", "finished": "已完成",
"followSystem": "跟隨系統", "followSystem": "跟隨系統",
"font": "字", "font": "字",
"fontSize": "字大小", "fontSize": "字大小",
"forExample": "例如", "forExample": "例如",
"force": "強制", "force": "強制",
"foundNUpdate": "找到 {count} 個更新", "foundNUpdate": "找到 {count} 個更新",
"fullScreen": "全模式", "fullScreen": "全螢幕模式",
"fullScreenJitter": "全模式抖動", "fullScreenJitter": "全螢幕模式抖動",
"fullScreenJitterHelp": "防止燒屏", "fullScreenJitterHelp": "防止燒屏",
"fullScreenTip": "當設備旋轉為橫屏時,是否開啟全模式?此選項僅適用於伺服器選項卡。", "fullScreenTip": "當設備旋轉為橫屏時,是否開啟全熒幕模式?此選項僅適用於伺服器選項卡。",
"getPushTokenFailed": "未能獲取到推送token", "getPushTokenFailed": "未能獲取到推送 token",
"gettingToken": "正在獲取Token...", "gettingToken": "正在獲取 Token...",
"goBackQ": "返回?", "goBackQ": "返回?",
"goto": "前往", "goto": "前往",
"hideTitleBar": "隱藏標題欄", "hideTitleBar": "隱藏標題欄",
@@ -130,57 +129,60 @@
"host": "主機", "host": "主機",
"hour": "小時", "hour": "小時",
"httpFailedWithCode": "請求失敗, 狀態碼: {code}", "httpFailedWithCode": "請求失敗, 狀態碼: {code}",
"icloudSynced": "iCloud已同步某些設置可能需要重啟才能生效。", "icloudSynced": "iCloud 已同步,某些設置可能需要重啟才能生效。",
"ignoreCert": "忽略證書", "ignoreCert": "忽略證書",
"image": "鏡像", "image": "鏡像",
"imagesList": "鏡像列表", "imagesList": "鏡像列表",
"import": "導入", "import": "導入",
"inAppUpdate": "在App內更新否則使用瀏覽器下載。", "inAppUpdate": "在 App 內更新?否則使用瀏覽器下載。",
"init": "初始化", "init": "初始化",
"inner": "內", "inner": "內",
"inputDomainHere": "在這裡輸入域名", "inputDomainHere": "在這裡輸入域名",
"install": "安裝", "install": "安裝",
"installDockerWithUrl": "請先 https://docs.docker.com/engine/install docker", "installDockerWithUrl": "請先 https://docs.docker.com/engine/install docker",
"invalid": "無效", "invalid": "無效",
"invalidJson": "無效的 JSON", "invalidJson": "無效的 JSON",
"invalidVersion": "不支持的版本", "invalidVersion": "不支持的版本",
"invalidVersionHelp": "請確保正確安裝了docker或者使用的非自編譯版本。如果沒有以上問題請在 {url} 提交問題。", "invalidVersionHelp": "請確保正確安裝了 docker或者使用的非自編譯版本。如果沒有以上問題請在 {url} 提交問題。",
"isBusy": "當前正忙", "isBusy": "當前正忙",
"jumpServer": "跳板服器", "jumpServer": "跳板服器",
"keepForeground": "請保持應用處於前台!", "keepForeground": "請保持應用處於前台!",
"keepStatusWhenErr": "保留上次的伺服器狀態", "keepStatusWhenErr": "保留上次的伺服器狀態",
"keepStatusWhenErrTip": "仅在执行脚本时出错时", "keepStatusWhenErrTip": "僅在執行腳本出錯時",
"keyAuth": "密鑰認證", "keyAuth": "密鑰認證",
"language": "語言", "language": "語言",
"languageName": "繁體中文", "languageName": "繁體中文",
"lastTry": "最後嘗試", "lastTry": "最後嘗試",
"launchPage": "啓動頁", "launchPage": "啓動頁",
"license": "開源證書", "letterCache": "输入法字符緩存",
"letterCacheTip": "建議關閉,但關閉後將無法輸入 CJK 等文字。",
"license": "開源許可證",
"light": "亮", "light": "亮",
"loadingFiles": "正在加載目錄。。。", "loadingFiles": "正在加載目錄...",
"location": "位置", "location": "位置",
"log": "日誌", "log": "日誌",
"loss": "丟包率", "loss": "丟包率",
"madeWithLove": "用❤️製作 by {myGithub}", "madeWithLove": "用❤️製作 by {myGithub}",
"manual": "手動", "manual": "手動",
"max": "最大", "max": "最大",
"maxRetryCount": "服器嘗試重連次數", "maxRetryCount": "服器嘗試重連次數",
"maxRetryCountEqual0": "會無限重試", "maxRetryCountEqual0": "會無限重試",
"min": "最小", "min": "最小",
"minute": "分鐘", "minute": "分鐘",
"mission": "任務", "mission": "任務",
"more": "更多", "more": "更多",
"moveOutServerFuncBtnsHelp": "開啟:可以在服器 Tab 頁的每個卡片下方顯示。關閉:在服器詳情頁頂部顯示。", "moveOutServerFuncBtnsHelp": "開啟:可以在服器 Tab 頁的每個卡片下方顯示。關閉:在服器詳情頁頂部顯示。",
"ms": "毫秒", "ms": "毫秒",
"name": "名稱", "name": "名稱",
"needHomeDir": "如果你是群暉用戶,[看這裡](https://kb.synology.com/DSM/tutorial/user_enable_home_service)。其他系統用戶需搜索如何創建家目錄home directory。", "needHomeDir": "如果你是群暉用戶,[看這裡](https://kb.synology.com/DSM/tutorial/user_enable_home_service)。其他系統用戶需搜索如何創建家目錄home directory。",
"needRestart": "需要重啓 App", "needRestart": "需要重啓 App",
"net": "網", "net": "網",
"netViewType": "網視圖類型", "netViewType": "網視圖類型",
"newContainer": "新建容器", "newContainer": "新建容器",
"noClient": "沒有SSH連接", "noClient": "沒有 SSH 連接",
"noInterface": "沒有可用的接口", "noInterface": "沒有可用的介面",
"noLineChart": "不使用折線圖", "noLineChart": "不使用折線圖",
"noLineChartForCpu": "CPU 不使用折線圖",
"noNotiPerm": "無通知權限,可能在下載應用程式更新時沒有進度提示。", "noNotiPerm": "無通知權限,可能在下載應用程式更新時沒有進度提示。",
"noOptions": "無可選項", "noOptions": "無可選項",
"noPrivateKeyTip": "私鑰不存在,可能已被刪除/配置錯誤。", "noPrivateKeyTip": "私鑰不存在,可能已被刪除/配置錯誤。",
@@ -188,45 +190,45 @@
"noResult": "無結果", "noResult": "無結果",
"noSavedPrivateKey": "沒有已保存的私鑰。", "noSavedPrivateKey": "沒有已保存的私鑰。",
"noSavedSnippet": "沒有已保存的程式片段。", "noSavedSnippet": "沒有已保存的程式片段。",
"noServerAvailable": "沒有可用的服器。", "noServerAvailable": "沒有可用的服器。",
"noTask": "沒有任務", "noTask": "沒有任務",
"noUpdateAvailable": "沒有可用更新", "noUpdateAvailable": "沒有可用更新",
"node": "節點", "node": "節點",
"notAvailable": "不可用", "notAvailable": "不可用",
"notSelected": "未選擇", "notSelected": "未選擇",
"note": "備註", "note": "備註",
"nullToken": "無Token", "nullToken": "無 Token",
"ok": "好", "ok": "好",
"onServerDetailPage": "在服器詳情頁", "onServerDetailPage": "在服器詳情頁",
"onlyOneLine": "僅顯示為一行(可滾動)", "onlyOneLine": "僅顯示為一行(可滾動)",
"onlyWhenCoreBiggerThan8": "僅當核心數>8時生效", "onlyWhenCoreBiggerThan8": "僅當核心數大於 8 時生效",
"open": "打開", "open": "打開",
"openLastPath": "打開上次的路徑", "openLastPath": "打開上次的路徑",
"openLastPathTip": "不同的服器會有不同的記錄,且記錄的是退出時的路徑", "openLastPathTip": "不同的服器會有不同的記錄,且記錄的是退出時的路徑",
"parseContainerStats": "解析容器佔用狀態", "parseContainerStatsTip": "Docker 解析佔用狀態較為緩慢",
"parseContainerStatsTip": "Docker解析佔用狀態較為緩慢",
"paste": "貼上", "paste": "貼上",
"path": "路徑", "path": "路徑",
"percentOfSize": "{size} 的 {percent}%", "percentOfSize": "{size} 的 {percent}%",
"pickFile": "選擇文件", "permission": "權限",
"pickFile": "選擇檔案",
"pingAvg": "平均:", "pingAvg": "平均:",
"pingInputIP": "請輸入目標IP或域名", "pingInputIP": "請輸入目標 IP 或域名",
"pingNoServer": "沒有服器可用於Ping\n請在服務器tab新增服器後再試", "pingNoServer": "沒有服器可用於 Ping\n請在伺服器 Tab 新增服器後再試",
"pkg": "包管理", "pkg": "包管理",
"pkgUpgradeTip": "請在更新前備份系統。", "pkgUpgradeTip": "請在更新前備份系統。",
"platformNotSupportUpdate": "當前平台不支持更新,請編譯最新碼後手動安裝", "platformNotSupportUpdate": "當前平台不支持更新,請編譯最新原始碼後手動安裝",
"plugInType": "插入類型", "plugInType": "插入類型",
"plzEnterHost": "請輸入主機", "plzEnterHost": "請輸入主機",
"plzSelectKey": "請選擇私鑰", "plzSelectKey": "請選擇私鑰",
"port": "端口", "port": "",
"preview": "預覽", "preview": "預覽",
"primaryColorSeed": "主要色調種子", "primaryColorSeed": "主要色調種子",
"privateKey": "私鑰", "privateKey": "私鑰",
"process": "程", "process": "程",
"pushToken": "消息推送 Token", "pushToken": "消息推送 Token",
"pveIgnoreCertTip": "不建議啟用,請注意安全風險!如果您使用的是 PVE 的默認證書,則需要啟用此選項。", "pveIgnoreCertTip": "不建議啟用,請注意安全風險!如果您使用的是 PVE 的默認證書,則需要啟用此選項。",
"pveLoginFailed": "登錄失敗。無法使用伺服器配置中的使用者名稱/密碼以Linux PAM方式登錄。", "pveLoginFailed": "登錄失敗。無法使用伺服器配置中的使用者名稱/密碼以 Linux PAM 方式登錄。",
"pveVersionLow": "此功能目前處於測試階段僅在PVE 8+上進行過測試。請謹慎使用。", "pveVersionLow": "此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。",
"pwd": "密碼", "pwd": "密碼",
"read": "读", "read": "读",
"reboot": "重启", "reboot": "重启",
@@ -239,7 +241,7 @@
"reportBugsOnGithubIssue": "請到 {url} 提交問題", "reportBugsOnGithubIssue": "請到 {url} 提交問題",
"restart": "重啓", "restart": "重啓",
"restore": "恢復", "restore": "恢復",
"restoreSuccess": "恢復成功需要重啓App來應用更改", "restoreSuccess": "恢復成功,需要重啓 App 來應用更改",
"result": "結果", "result": "結果",
"rotateAngel": "旋轉角度", "rotateAngel": "旋轉角度",
"route": "路由", "route": "路由",
@@ -248,34 +250,36 @@
"save": "保存", "save": "保存",
"saved": "已保存", "saved": "已保存",
"second": "秒", "second": "秒",
"sensors": "感器", "sensors": "感器",
"sequence": "順序", "sequence": "順序",
"server": "服器", "server": "服器",
"serverDetailOrder": "詳情頁部件順序", "serverDetailOrder": "詳情頁部件順序",
"serverFuncBtns": "服器功能按鈕", "serverFuncBtns": "服器功能按鈕",
"serverOrder": "服器順序", "serverOrder": "服器順序",
"serverTabConnecting": "連接中...", "serverTabConnecting": "連接中...",
"serverTabEmpty": "現在沒有服器。\n點擊右下方按鈕來新增。", "serverTabEmpty": "現在沒有服器。\n點擊右下方按鈕來新增。",
"serverTabFailed": "失敗", "serverTabFailed": "失敗",
"serverTabLoading": "加載中...", "serverTabLoading": "加載中...",
"serverTabPlzSave": "請再次保存該私鑰", "serverTabPlzSave": "請再次保存該私鑰",
"serverTabUnkown": "未知狀態", "serverTabUnkown": "未知狀態",
"setting": "設置", "setting": "設置",
"sftpDlPrepare": "準備連接至服器...", "sftpDlPrepare": "準備連接至服器...",
"sftpEditorTip": "如果為空, 使用App內置的文件編輯器。如果有值, 則使用遠程伺服器的編輯器, 例如 `vim`(建議根據 `EDITOR` 自動獲取)。",
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除文件夾", "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除文件夾",
"sftpSSHConnected": "SFTP 已連接...", "sftpSSHConnected": "SFTP 已連接...",
"sftpShowFoldersFirst": "文件夾顯示在前", "sftpShowFoldersFirst": "資料夾顯示在前",
"showDistLogo": "顯示發行版 Logo", "showDistLogo": "顯示發行版 Logo",
"shutdown": "关机", "shutdown": "關機",
"size": "大小", "size": "大小",
"snippet": "程式片段", "snippet": "程式片段",
"softWrap": "軟換行", "softWrap": "軟換行",
"speed": "速度", "speed": "速度",
"spentTime": "耗時: {time}", "spentTime": "耗時: {time}",
"sshTermHelp": "在終端可滾動時,橫向拖動可以選中文字。點擊鍵盤按鈕可以開啟/關閉鍵盤。文件圖標會打開當前路徑SFTP。剪貼按鈕會在有選中文字時複製內容,在未選中並且剪貼有內容時貼上內容到終端。代碼圖標會貼上代碼片段到終端並執行。", "sshTermHelp": "在終端可滾動時,橫向拖動可以選中文字。點擊鍵盤按鈕可以開啟/關閉鍵盤。文件圖標會打開當前路徑 SFTP。剪貼簿按鈕會在有選中文字時複製內容,在未選中並且剪貼簿有內容時貼上內容到終端。代碼圖標會貼上代碼片段到終端並執行。",
"sshTip": "該功能目前處於測試階段。\n\n請在 {url} 反饋問題,或者加入我們開發。", "sshTip": "該功能目前處於測試階段。\n\n請在 {url} 反饋問題,或者加入我們開發。",
"sshVirtualKeyAutoOff": "虛擬按鍵自動切換", "sshVirtualKeyAutoOff": "虛擬按鍵自動切換",
"start": "開始", "start": "開始",
"stat": "統計",
"stats": "統計", "stats": "統計",
"stop": "停止", "stop": "停止",
"stopped": "已停止", "stopped": "已停止",
@@ -290,53 +294,54 @@
"system": "系統", "system": "系統",
"tag": "标签", "tag": "标签",
"temperature": "溫度", "temperature": "溫度",
"termFontSizeTip": "此設置將影響終端大小(寬度和高度)。您可以在終端頁面縮放,來調整當前會話的字大小。", "termFontSizeTip": "此設置將影響終端大小(寬度和高度)。您可以在終端頁面縮放,來調整當前會話的字大小。",
"terminal": "终端機", "terminal": "终端機",
"test": "測試", "test": "測試",
"textScaler": "字縮放", "textScaler": "字縮放",
"textScalerTip": "1.0 => 100%(原大小),僅作用於伺服器頁面部分字,不建議修改。", "textScalerTip": "1.0 => 100%(原大小),僅作用於伺服器頁面部分字,不建議修改。",
"theme": "主題", "theme": "主題",
"themeMode": "主題模式", "themeMode": "主題模式",
"time": "時間", "time": "時間",
"times": "次", "times": "次",
"total": "總共", "total": "總共",
"traffic": "流量", "traffic": "流量",
"trySudo": "嘗試使用sudo", "trySudo": "嘗試使用 sudo",
"ttl": "緩存時間", "ttl": "TTL",
"unknown": "未知", "unknown": "未知",
"unknownError": "未知錯誤", "unknownError": "未知錯誤",
"unkownConvertMode": "未知轉換模式", "unkownConvertMode": "未知轉換模式",
"update": "更新", "update": "更新",
"updateAll": "更新全部", "updateAll": "更新全部",
"updateIntervalEqual0": "你設置為0器狀態不會自動更新。\n且不能計算CPU使用情況。", "updateIntervalEqual0": "你設置為 0服器狀態不會自動更新。\n且不能計算CPU使用情況。",
"updateServerStatusInterval": "服器狀態更新間隔", "updateServerStatusInterval": "服器狀態更新間隔",
"updateTip": "新版本: v1.0.{newest}", "updateTip": "新版本: v1.0.{newest}",
"updateTipTooLow": "當前版本過低,請升級至 v1.0.{newest}", "updateTipTooLow": "當前版本過低,請升級至 v1.0.{newest}",
"upload": "上傳", "upload": "上傳",
"upsideDown": "上下交換", "upsideDown": "上下交換",
"uptime": "啟動時長", "uptime": "啟動時長",
"urlOrJson": "鏈接或JSON", "urlOrJson": "鏈接或 JSON",
"useCdn": "使用CDN", "useCdn": "使用 CDN",
"useCdnTip": "非中國大用戶建議使用CDN是否使用", "useCdnTip": "非中國大用戶建議使用 CDN是否使用",
"useNoPwd": "将使用無密碼", "useNoPwd": "将使用無密碼",
"usePodmanByDefault": "默認使用 Podman", "usePodmanByDefault": "默認使用 Podman",
"used": "已用", "used": "已用",
"user": "用戶", "user": "使用者",
"versionHaveUpdate": "找到新版本v1.0.{build}, 點擊更新", "versionHaveUpdate": "找到新版本v1.0.{build}, 點擊更新",
"versionUnknownUpdate": "當前v1.0.{build},點擊檢查更新", "versionUnknownUpdate": "當前v1.0.{build},點擊檢查更新",
"versionUpdated": "當前v1.0.{build}, 已是最新版本", "versionUpdated": "當前v1.0.{build}, 已是最新版本",
"view": "視圖", "view": "視圖",
"viewErr": "查看錯誤", "viewErr": "查看錯誤",
"virtKeyHelpClipboard": "如果終端有選中字,則復製選中字至剪切板,否則粘貼剪切板內容至終端。", "virtKeyHelpClipboard": "如果終端有選中字,則復製選中字至剪貼簿,否則粘貼剪貼簿內容至終端。",
"virtKeyHelpIME": "打開/關閉鍵盤", "virtKeyHelpIME": "打開/關閉鍵盤",
"virtKeyHelpSFTP": "在 SFTP 中打開當前路徑。", "virtKeyHelpSFTP": "在 SFTP 中打開當前路徑。",
"waitConnection": "請等待連接建立", "waitConnection": "請等待連接建立",
"wakeLock": "保持喚醒", "wakeLock": "保持喚醒",
"watchNotPaired": "沒有已配對的 Apple Watch", "watchNotPaired": "沒有已配對的 Apple Watch",
"webdavSettingEmpty": "Webdav 設置項爲空", "webdavSettingEmpty": "WebDav 設置項爲空",
"whenOpenApp": "當打開 App 時", "whenOpenApp": "當打開 App 時",
"willTakEeffectImmediately": "更改將會立即生效", "willTakEeffectImmediately": "更改將會立即生效",
"wolTip": "在配置WOL網絡喚醒每次連接伺服器都會先發送一次WOL請求。", "wolTip": "在配置 WOL網絡喚醒每次連接伺服器都會先發送一次 WOL 請求。",
"write": "写", "write": "写",
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。" "writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
"writeScriptTip": "連接到伺服器後,將會在 ~/.config/server_box 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。"
} }

View File

@@ -9,7 +9,6 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:server_box/app.dart'; import 'package:server_box/app.dart';
import 'package:server_box/core/utils/sync/icloud.dart'; import 'package:server_box/core/utils/sync/icloud.dart';
import 'package:server_box/core/utils/sync/webdav.dart'; import 'package:server_box/core/utils/sync/webdav.dart';
@@ -54,9 +53,7 @@ void _runInZone(void Function() body) {
runZonedGuarded( runZonedGuarded(
body, body,
(obj, trace) { (e, s) => print('[ZONE] $e\n$s'),
Loggers.root.warning(obj, null, trace);
},
zoneSpecification: zoneSpec, zoneSpecification: zoneSpec,
); );
} }
@@ -70,11 +67,12 @@ Future<void> _initApp() async {
final windowSize = Stores.setting.windowSize; final windowSize = Stores.setting.windowSize;
final hideTitleBar = Stores.setting.hideTitleBar.fetch(); final hideTitleBar = Stores.setting.hideTitleBar.fetch();
SystemUIs.initDesktopWindow( await SystemUIs.initDesktopWindow(
hideTitleBar: hideTitleBar, hideTitleBar: hideTitleBar,
size: windowSize.fetch().toSize(), size: windowSize.fetch().toSize(),
listener: WindowSizeListener(windowSize), listener: WindowSizeListener(windowSize),
); );
FontUtils.loadFrom(Stores.setting.fontPath.fetch()); FontUtils.loadFrom(Stores.setting.fontPath.fetch());
_doPlatformRelated(); _doPlatformRelated();
@@ -82,7 +80,6 @@ Future<void> _initApp() async {
} }
Future<void> _initData() async { Future<void> _initData() async {
// await SecureStore.init();
await Hive.initFlutter(); await Hive.initFlutter();
// Ordered by typeId // Ordered by typeId
Hive.registerAdapter(PrivateKeyInfoAdapter()); // 1 Hive.registerAdapter(PrivateKeyInfoAdapter()); // 1
@@ -94,6 +91,8 @@ Future<void> _initData() async {
Hive.registerAdapter(ServerCustomAdapter()); // 7 Hive.registerAdapter(ServerCustomAdapter()); // 7
Hive.registerAdapter(WakeOnLanCfgAdapter()); // 8 Hive.registerAdapter(WakeOnLanCfgAdapter()); // 8
await PrefStore.init(); // Call this before accessing any store
await Stores.setting.init(); await Stores.setting.init();
await Stores.server.init(); await Stores.server.init();
await Stores.key.init(); await Stores.key.init();
@@ -120,8 +119,6 @@ void _setupDebug() {
void _doPlatformRelated() async { void _doPlatformRelated() async {
if (isAndroid) { if (isAndroid) {
// SharedPreferences is only used on Android for saving home widgets settings.
SharedPreferences.setPrefix('');
// try switch to highest refresh rate // try switch to highest refresh rate
FlutterDisplayMode.setHighRefreshRate(); FlutterDisplayMode.setHighRefreshRate();
} }

View File

@@ -225,6 +225,7 @@ class BackupPage extends StatelessWidget {
final backup = await context.showLoadingDialog( final backup = await context.showLoadingDialog(
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()), fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
); );
if (backup == null) return;
if (backupFormatVersion != backup.version) { if (backupFormatVersion != backup.version) {
context.showSnackBar(l10n.backupVersionNotMatch); context.showSnackBar(l10n.backupVersionNotMatch);
return; return;
@@ -305,6 +306,8 @@ class BackupPage extends StatelessWidget {
final url = TextEditingController(text: Stores.setting.webdavUrl.fetch()); final url = TextEditingController(text: Stores.setting.webdavUrl.fetch());
final user = TextEditingController(text: Stores.setting.webdavUser.fetch()); final user = TextEditingController(text: Stores.setting.webdavUser.fetch());
final pwd = TextEditingController(text: Stores.setting.webdavPwd.fetch()); final pwd = TextEditingController(text: Stores.setting.webdavPwd.fetch());
final nodeUser = FocusNode();
final nodePwd = FocusNode();
final result = await context.showRoundDialog<bool>( final result = await context.showRoundDialog<bool>(
title: 'WebDAV', title: 'WebDAV',
child: Column( child: Column(
@@ -314,14 +317,22 @@ class BackupPage extends StatelessWidget {
label: 'URL', label: 'URL',
hint: 'https://example.com/webdav/', hint: 'https://example.com/webdav/',
controller: url, controller: url,
suggestion: false,
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodeUser),
), ),
Input( Input(
label: l10n.user, label: l10n.user,
controller: user, controller: user,
node: nodeUser,
suggestion: false,
onSubmitted: (p0) => FocusScope.of(context).requestFocus(nodePwd),
), ),
Input( Input(
label: l10n.pwd, label: l10n.pwd,
controller: pwd, controller: pwd,
node: nodePwd,
suggestion: false,
onSubmitted: (_) => context.pop(true),
), ),
], ],
), ),
@@ -357,6 +368,7 @@ class BackupPage extends StatelessWidget {
fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()), fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()),
); );
if (backup == null) return;
if (backupFormatVersion != backup.version) { if (backupFormatVersion != backup.version) {
context.showSnackBar(l10n.backupVersionNotMatch); context.showSnackBar(l10n.backupVersionNotMatch);
return; return;
@@ -398,6 +410,7 @@ class BackupPage extends StatelessWidget {
return list.map((e) => ServerPrivateInfo.fromJson(e)).toList(); return list.map((e) => ServerPrivateInfo.fromJson(e)).toList();
}, text.trim()), }, text.trim()),
); );
if (spis == null) return;
final sure = await context.showRoundDialog<bool>( final sure = await context.showRoundDialog<bool>(
title: l10n.import, title: l10n.import,
child: Text(l10n.askContinue('${spis.length} ${l10n.server}')), child: Text(l10n.askContinue('${spis.length} ${l10n.server}')),
@@ -409,13 +422,15 @@ class BackupPage extends StatelessWidget {
], ],
); );
if (sure == true) { if (sure == true) {
await context.showLoadingDialog( final suc = await context.showLoadingDialog(
fn: () async { fn: () async {
for (var spi in spis) { for (var spi in spis) {
Stores.server.put(spi); Stores.server.put(spi);
} }
return true;
}, },
); );
if (suc != true) return;
context.showSnackBar(l10n.success); context.showSnackBar(l10n.success);
} }
} catch (e, s) { } catch (e, s) {

View File

@@ -338,18 +338,21 @@ class _ContainerPageState extends State<ContainerPage> {
label: l10n.image, label: l10n.image,
hint: 'xxx:1.1', hint: 'xxx:1.1',
controller: imageCtrl, controller: imageCtrl,
suggestion: false,
), ),
Input( Input(
type: TextInputType.text, type: TextInputType.text,
controller: nameCtrl, controller: nameCtrl,
label: l10n.containerName, label: l10n.containerName,
hint: 'xxx', hint: 'xxx',
suggestion: false,
), ),
Input( Input(
type: TextInputType.text, type: TextInputType.text,
controller: argsCtrl, controller: argsCtrl,
label: l10n.extraArgs, label: l10n.extraArgs,
hint: '-p 2222:22 -v ~/.xxx/:/xxx', hint: '-p 2222:22 -v ~/.xxx/:/xxx',
suggestion: false,
), ),
], ],
), ),
@@ -425,6 +428,7 @@ class _ContainerPageState extends State<ContainerPage> {
controller: ctrl, controller: ctrl,
onSubmitted: _onSaveDockerHost, onSubmitted: _onSaveDockerHost,
hint: 'unix:///run/user/1000/docker.sock', hint: 'unix:///run/user/1000/docker.sock',
suggestion: false,
), ),
actions: [ actions: [
TextButton( TextButton(

View File

@@ -145,10 +145,10 @@ class _EditorPageState extends State<EditorPage> {
// If path is not null, then it's a file editor // If path is not null, then it's a file editor
// save the text and return true to pop the page // save the text and return true to pop the page
if (widget.path != null) { if (widget.path != null) {
await context.showLoadingDialog( final res = await context.showLoadingDialog(
fn: () => File(widget.path!).writeAsString(_controller.text), fn: () => File(widget.path!).writeAsString(_controller.text),
); );
if (res == null) return;
context.pop(true); context.pop(true);
return; return;
} }

View File

@@ -14,6 +14,8 @@ final class _AppBar extends CustomAppBar {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isDesktop) return super.build(context);
final placeholder = SizedBox( final placeholder = SizedBox(
height: CustomAppBar.barHeight ?? 0 + MediaQuery.of(context).padding.top, height: CustomAppBar.barHeight ?? 0 + MediaQuery.of(context).padding.top,
); );
@@ -25,7 +27,7 @@ final class _AppBar extends CustomAppBar {
return ValBuilder( return ValBuilder(
listenable: selectIndex, listenable: selectIndex,
builder: (idx) { builder: (idx) {
if (idx == AppTab.ssh.index && !isWindows && !isLinux) { if (idx == AppTab.ssh.index) {
return placeholder; return placeholder;
} }
return super.build(context); return super.build(context);

View File

@@ -78,11 +78,7 @@ class _HomePageState extends State<HomePage>
switch (state) { switch (state) {
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
if (_shouldAuth) { if (_shouldAuth) _goAuth();
if (Stores.setting.useBioAuth.fetch()) {
BioAuth.go().then((_) => _shouldAuth = false);
}
}
if (!Pros.server.isAutoRefreshOn) { if (!Pros.server.isAutoRefreshOn) {
Pros.server.startAutoRefresh(); Pros.server.startAutoRefresh();
} }
@@ -323,7 +319,7 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
@override @override
Future<void> afterFirstLayout(BuildContext context) async { Future<void> afterFirstLayout(BuildContext context) async {
// Auth required for first launch // Auth required for first launch
if (Stores.setting.useBioAuth.fetch()) BioAuth.go(); _goAuth();
//_reqNotiPerm(); //_reqNotiPerm();
@@ -361,6 +357,16 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
// } // }
// } // }
void _goAuth() {
if (Stores.setting.useBioAuth.fetch()) {
if (BioAuthPage.route.isAlreadyIn) return;
BioAuthPage.route.go(
context,
args: BioAuthPageArgs(onAuthSuccess: () => _shouldAuth = false),
);
}
}
Future<void> _onLongPressSetting() async { Future<void> _onLongPressSetting() async {
final map = Stores.setting.box.toJson(includeInternal: false); final map = Stores.setting.box.toJson(includeInternal: false);
final keys = map.keys; final keys = map.keys;

View File

@@ -52,12 +52,14 @@ class _IPerfPageState extends State<IPerfPage> {
controller: _hostCtrl, controller: _hostCtrl,
label: l10n.host, label: l10n.host,
icon: Icons.computer, icon: Icons.computer,
suggestion: false,
), ),
Input( Input(
controller: _portCtrl, controller: _portCtrl,
label: l10n.port, label: l10n.port,
type: TextInputType.number, type: TextInputType.number,
icon: Icons.numbers, icon: Icons.numbers,
suggestion: false,
), ),
], ],
); );

View File

@@ -59,6 +59,7 @@ class _PingPageState extends State<PingPage>
controller: _textEditingController, controller: _textEditingController,
hint: l10n.inputDomainHere, hint: l10n.inputDomainHere,
maxLines: 1, maxLines: 1,
suggestion: false,
onSubmitted: (_) => _doPing(), onSubmitted: (_) => _doPing(),
), ),
actions: [ actions: [

View File

@@ -118,32 +118,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
Widget _buildFAB() { Widget _buildFAB() {
return FloatingActionButton( return FloatingActionButton(
tooltip: l10n.save, tooltip: l10n.save,
onPressed: () async { onPressed: _onTapSave,
final name = _nameController.text;
final key = _standardizeLineSeparators(_keyController.text.trim());
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
context.showSnackBar(l10n.fieldMustNotEmpty);
return;
}
FocusScope.of(context).unfocus();
_loading.value = UIs.centerSizedLoading;
try {
final decrypted = await Computer.shared.start(decyptPem, [key, pwd]);
final pki = PrivateKeyInfo(id: name, key: decrypted);
if (widget.pki != null) {
Pros.key.update(widget.pki!, pki);
} else {
Pros.key.add(pki);
}
} catch (e) {
context.showSnackBar(e.toString());
rethrow;
} finally {
_loading.value = null;
}
context.pop();
},
child: const Icon(Icons.save), child: const Icon(Icons.save),
); );
} }
@@ -160,6 +135,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
onSubmitted: (_) => _focusScope.requestFocus(_keyNode), onSubmitted: (_) => _focusScope.requestFocus(_keyNode),
label: l10n.name, label: l10n.name,
icon: Icons.info, icon: Icons.info,
suggestion: true,
), ),
Input( Input(
controller: _keyController, controller: _keyController,
@@ -170,6 +146,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
onSubmitted: (_) => _focusScope.requestFocus(_pwdNode), onSubmitted: (_) => _focusScope.requestFocus(_pwdNode),
label: l10n.privateKey, label: l10n.privateKey,
icon: Icons.vpn_key, icon: Icons.vpn_key,
suggestion: false,
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@@ -206,6 +183,8 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
obscureText: true, obscureText: true,
label: l10n.pwd, label: l10n.pwd,
icon: Icons.password, icon: Icons.password,
suggestion: false,
onSubmitted: (_) => _onTapSave(),
), ),
SizedBox(height: MediaQuery.of(context).size.height * 0.1), SizedBox(height: MediaQuery.of(context).size.height * 0.1),
ValBuilder( ValBuilder(
@@ -215,4 +194,31 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
], ],
); );
} }
void _onTapSave() async {
final name = _nameController.text;
final key = _standardizeLineSeparators(_keyController.text.trim());
final pwd = _pwdController.text;
if (name.isEmpty || key.isEmpty) {
context.showSnackBar(l10n.fieldMustNotEmpty);
return;
}
FocusScope.of(context).unfocus();
_loading.value = UIs.centerSizedLoading;
try {
final decrypted = await Computer.shared.start(decyptPem, [key, pwd]);
final pki = PrivateKeyInfo(id: name, key: decrypted);
if (widget.pki != null) {
Pros.key.update(widget.pki!, pki);
} else {
Pros.key.add(pki);
}
} catch (e) {
context.showSnackBar(e.toString());
rethrow;
} finally {
_loading.value = null;
}
context.pop();
}
} }

View File

@@ -430,10 +430,9 @@ final class _PvePageState extends State<PvePage> {
], ],
); );
if (sure != true) return; if (sure != true) return;
bool? suc;
await context.showLoadingDialog(fn: () async { final suc =
suc = await func(item.node, item.id); await context.showLoadingDialog(fn: () => func(item.node, item.id));
});
if (suc == true) { if (suc == true) {
context.showSnackBar(l10n.success); context.showSnackBar(l10n.success);
} else { } else {

View File

@@ -82,6 +82,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
final s = widget.spi.server; final s = widget.spi.server;
if (s == null) { if (s == null) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(),
body: Center( body: Center(
child: Text(l10n.noClient), child: Text(l10n.noClient),
), ),
@@ -197,6 +198,29 @@ class _ServerDetailPageState extends State<ServerDetailPage>
]); ]);
} }
final List<Widget> children = Stores.setting.cpuViewAsProgress.fetch()
? _buildCPUProgress(ss.cpu)
: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
child: SizedBox(
height: 137,
width: _media.size.width - 26 - 34,
child: _buildLineChart(
ss.cpu.spots,
//ss.cpu.rangeX,
tooltipPrefix: 'CPU',
),
),
),
];
if (ss.cpu.brand.isNotEmpty) {
children.add(Column(
children: ss.cpu.brand.entries.map(_buildCpuModelItem).toList(),
).paddingOnly(top: 13));
}
return CardX( return CardX(
child: ExpandTile( child: ExpandTile(
title: Align( title: Align(
@@ -213,27 +237,30 @@ class _ServerDetailPageState extends State<ServerDetailPage>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: details, children: details,
), ),
children: Stores.setting.cpuViewAsProgress.fetch() children: children,
? _buildCPUProgress(ss.cpu)
: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
child: SizedBox(
height: 137,
width: _media.size.width - 26 - 34,
child: _buildLineChart(
ss.cpu.spots,
//ss.cpu.rangeX,
tooltipPrefix: 'CPU',
),
),
),
],
), ),
); );
} }
Widget _buildCpuModelItem(MapEntry<String, int> e) {
final child = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: _media.size.width * .7,
child: Text(
e.key,
style: UIs.text13,
overflow: TextOverflow.fade,
maxLines: 1,
),
),
Text('x ${e.value}', style: UIs.text13Grey),
],
);
return child.paddingSymmetric(horizontal: 17);
}
Widget _buildDetailPercent(double percent, String timeType) { Widget _buildDetailPercent(double percent, String timeType) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -873,6 +900,6 @@ class _ServerDetailPageState extends State<ServerDetailPage>
bool _getInitExpand(int len, [int? max]) { bool _getInitExpand(int len, [int? max]) {
if (!_collapse) return true; if (!_collapse) return true;
return len <= (max ?? 3); return len > 0 && len <= (max ?? 3);
} }
} }

View File

@@ -1,16 +1,11 @@
import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:server_box/data/model/server/wol_cfg.dart'; import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:server_box/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/res/url.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
import '../../../data/model/server/server_private_info.dart'; import '../../../data/model/server/server_private_info.dart';
@@ -25,7 +20,7 @@ class ServerEditPage extends StatefulWidget {
State<ServerEditPage> createState() => _ServerEditPageState(); State<ServerEditPage> createState() => _ServerEditPageState();
} }
class _ServerEditPageState extends State<ServerEditPage> { class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
final _nameController = TextEditingController(); final _nameController = TextEditingController();
final _ipController = TextEditingController(); final _ipController = TextEditingController();
final _altUrlController = TextEditingController(); final _altUrlController = TextEditingController();
@@ -33,7 +28,6 @@ class _ServerEditPageState extends State<ServerEditPage> {
final _usernameController = TextEditingController(); final _usernameController = TextEditingController();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
final _pveAddrCtrl = TextEditingController(); final _pveAddrCtrl = TextEditingController();
final _customCmdCtrl = TextEditingController();
final _preferTempDevCtrl = TextEditingController(); final _preferTempDevCtrl = TextEditingController();
final _logoUrlCtrl = TextEditingController(); final _logoUrlCtrl = TextEditingController();
final _wolMacCtrl = TextEditingController(); final _wolMacCtrl = TextEditingController();
@@ -52,58 +46,11 @@ class _ServerEditPageState extends State<ServerEditPage> {
final _autoConnect = ValueNotifier(true); final _autoConnect = ValueNotifier(true);
final _jumpServer = ValueNotifier<String?>(null); final _jumpServer = ValueNotifier<String?>(null);
final _pveIgnoreCert = ValueNotifier(false); final _pveIgnoreCert = ValueNotifier(false);
final _env = <String, String>{}.vn;
final _customCmds = <String, String>{}.vn;
var _tags = <String>[]; var _tags = <String>[];
@override
void initState() {
super.initState();
final spi = widget.spi;
if (spi != null) {
_nameController.text = spi.name;
_ipController.text = spi.ip;
_portController.text = spi.port.toString();
_usernameController.text = spi.user;
if (spi.keyId == null) {
_passwordController.text = spi.pwd ?? '';
} else {
_keyIdx.value = Pros.key.pkis.indexWhere(
(e) => e.id == widget.spi!.keyId,
);
}
/// List in dart is passed by pointer, so you need to copy it here
_tags.addAll(spi.tags ?? []);
_altUrlController.text = spi.alterUrl ?? '';
_autoConnect.value = spi.autoConnect ?? true;
_jumpServer.value = spi.jumpId;
final custom = spi.custom;
if (custom != null) {
_pveAddrCtrl.text = custom.pveAddr ?? '';
_pveIgnoreCert.value = custom.pveIgnoreCert;
try {
// Add a null check here to prevent setting `null` to the controller
final encoded = json.encode(custom.cmds!);
if (encoded.isNotEmpty) {
_customCmdCtrl.text = encoded;
}
} catch (_) {}
_preferTempDevCtrl.text = custom.preferTempDev ?? '';
_logoUrlCtrl.text = custom.logoUrl ?? '';
}
final wol = spi.wolCfg;
if (wol != null) {
_wolMacCtrl.text = wol.mac;
_wolIpCtrl.text = wol.ip;
_wolPwdCtrl.text = wol.pwd ?? '';
}
}
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
@@ -119,7 +66,6 @@ class _ServerEditPageState extends State<ServerEditPage> {
_portFocus.dispose(); _portFocus.dispose();
_usernameFocus.dispose(); _usernameFocus.dispose();
_pveAddrCtrl.dispose(); _pveAddrCtrl.dispose();
_customCmdCtrl.dispose();
_preferTempDevCtrl.dispose(); _preferTempDevCtrl.dispose();
_logoUrlCtrl.dispose(); _logoUrlCtrl.dispose();
_wolMacCtrl.dispose(); _wolMacCtrl.dispose();
@@ -135,10 +81,13 @@ class _ServerEditPageState extends State<ServerEditPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GestureDetector(
appBar: _buildAppBar(), onTap: () => _focusScope.unfocus(),
body: _buildForm(), child: Scaffold(
floatingActionButton: _buildFAB(), appBar: _buildAppBar(),
body: _buildForm(),
floatingActionButton: _buildFAB(),
),
); );
} }
@@ -152,44 +101,17 @@ class _ServerEditPageState extends State<ServerEditPage> {
Widget _buildDelBtn() { Widget _buildDelBtn() {
return IconButton( return IconButton(
onPressed: () { onPressed: () {
var delScripts = false;
context.showRoundDialog( context.showRoundDialog(
title: l10n.attention, title: l10n.attention,
child: StatefulBuilder(builder: (ctx, setState) { child: StatefulBuilder(builder: (ctx, setState) {
return Column( return Text(l10n.askContinue(
mainAxisSize: MainAxisSize.min, '${l10n.delete} ${l10n.server}(${widget.spi!.name})',
crossAxisAlignment: CrossAxisAlignment.start, ));
children: [
Text(l10n.askContinue(
'${l10n.delete} ${l10n.server}(${widget.spi!.name})',
)),
UIs.height13,
if (widget.spi?.server?.canViewDetails ?? false)
CheckboxListTile(
value: delScripts,
onChanged: (_) => setState(
() => delScripts = !delScripts,
),
controlAffinity: ListTileControlAffinity.leading,
title: Text(l10n.deleteScripts),
tileColor: Colors.transparent,
contentPadding: EdgeInsets.zero,
)
],
);
}), }),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
context.pop(); context.pop();
if (delScripts) {
await context.showLoadingDialog(
fn: () async {
const cmd = 'rm ${ShellFunc.srvBoxDir}/mobile_v*.sh';
return widget.spi?.server?.client?.run(cmd);
},
);
}
Pros.server.delServer(widget.spi!.id); Pros.server.delServer(widget.spi!.id);
context.pop(true); context.pop(true);
}, },
@@ -204,6 +126,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
Widget _buildForm() { Widget _buildForm() {
final children = [ final children = [
_buildWriteScriptTip(),
Input( Input(
autoFocus: true, autoFocus: true,
controller: _nameController, controller: _nameController,
@@ -225,6 +148,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.host, label: l10n.host,
icon: BoxIcons.bx_server, icon: BoxIcons.bx_server,
hint: 'example.com', hint: 'example.com',
suggestion: false,
), ),
Input( Input(
controller: _portController, controller: _portController,
@@ -234,6 +158,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.port, label: l10n.port,
icon: Bootstrap.number_123, icon: Bootstrap.number_123,
hint: '22', hint: '22',
suggestion: false,
), ),
Input( Input(
controller: _usernameController, controller: _usernameController,
@@ -243,14 +168,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.user, label: l10n.user,
icon: Icons.account_box, icon: Icons.account_box,
hint: 'root', hint: 'root',
), suggestion: false,
Input(
controller: _altUrlController,
type: TextInputType.url,
node: _alterUrlFocus,
label: l10n.alterUrl,
icon: MingCute.link_line,
hint: 'user@ip:port',
), ),
TagEditor( TagEditor(
tags: _tags, tags: _tags,
@@ -275,7 +193,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
_buildMore(), _buildMore(),
]; ];
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(17, 17, 17, 47), padding: const EdgeInsets.fromLTRB(17, 7, 17, 47),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -317,6 +235,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.pwd, label: l10n.pwd,
icon: Icons.password, icon: Icons.password,
hint: l10n.pwd, hint: l10n.pwd,
suggestion: false,
onSubmitted: (_) => _onSave(), onSubmitted: (_) => _onSave(),
)); ));
} }
@@ -326,12 +245,13 @@ class _ServerEditPageState extends State<ServerEditPage> {
} }
Widget _buildKeyAuth() { Widget _buildKeyAuth() {
const padding = EdgeInsets.only(left: 23, right: 13);
return Consumer<PrivateKeyProvider>( return Consumer<PrivateKeyProvider>(
builder: (_, key, __) { builder: (_, key, __) {
final tiles = List<Widget>.generate(key.pkis.length, (index) { final tiles = List<Widget>.generate(key.pkis.length, (index) {
final e = key.pkis[index]; final e = key.pkis[index];
return ListTile( return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 17), contentPadding: padding,
leading: Text( leading: Text(
'#${index + 1}', '#${index + 1}',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
@@ -353,7 +273,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(l10n.addPrivateKey), title: Text(l10n.addPrivateKey),
contentPadding: const EdgeInsets.symmetric(horizontal: 17), contentPadding: padding,
trailing: const Padding( trailing: const Padding(
padding: EdgeInsets.only(right: 13), padding: EdgeInsets.only(right: 13),
child: Icon(Icons.add), child: Icon(Icons.add),
@@ -371,19 +291,45 @@ class _ServerEditPageState extends State<ServerEditPage> {
); );
} }
Widget _buildEnvs() {
return _env.listenVal((val) {
final subtitle =
val.isEmpty ? null : Text(val.keys.join(','), style: UIs.textGrey);
return ListTile(
leading: const Padding(
padding: EdgeInsets.only(left: 10),
child: Icon(HeroIcons.variable),
),
subtitle: subtitle,
title: Text(l10n.envVars),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () async {
final res = await KvEditor.route.go(
context,
args: KvEditorArgs(data: widget.spi?.envs ?? {}),
);
if (res == null) return;
_env.value = res;
},
).cardx;
});
}
Widget _buildMore() { Widget _buildMore() {
return ExpandTile( return ExpandTile(
title: Text(l10n.more), title: Text(l10n.more),
children: [ children: [
const Text('Logo', style: UIs.text13Grey),
UIs.height7, UIs.height7,
Input( Input(
controller: _logoUrlCtrl, controller: _logoUrlCtrl,
type: TextInputType.url, type: TextInputType.url,
icon: Icons.image, icon: Icons.image,
label: 'Url', label: 'Logo URL',
hint: 'https://example.com/logo.png', hint: 'https://example.com/logo.png',
suggestion: false,
), ),
_buildAltUrl(),
_buildEnvs(),
UIs.height7, UIs.height7,
..._buildPVEs(), ..._buildPVEs(),
UIs.height7, UIs.height7,
@@ -397,6 +343,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.deviceName, label: l10n.deviceName,
icon: MingCute.low_temperature_line, icon: MingCute.low_temperature_line,
hint: 'nvme-pci-0400', hint: 'nvme-pci-0400',
suggestion: false,
), ),
UIs.height7, UIs.height7,
..._buildWOLs(), ..._buildWOLs(),
@@ -404,6 +351,18 @@ class _ServerEditPageState extends State<ServerEditPage> {
); );
} }
Widget _buildAltUrl() {
return Input(
controller: _altUrlController,
type: TextInputType.url,
node: _alterUrlFocus,
label: l10n.fallbackSshDest,
icon: MingCute.link_line,
hint: 'user@ip:port',
suggestion: false,
);
}
List<Widget> _buildPVEs() { List<Widget> _buildPVEs() {
const addr = 'https://127.0.0.1:8006'; const addr = 'https://127.0.0.1:8006';
return [ return [
@@ -423,8 +382,9 @@ class _ServerEditPageState extends State<ServerEditPage> {
type: TextInputType.url, type: TextInputType.url,
icon: MingCute.web_line, icon: MingCute.web_line,
node: node, node: node,
label: l10n.addr, label: 'URL',
hint: addr, hint: addr,
suggestion: false,
), ),
), ),
ListTile( ListTile(
@@ -451,14 +411,26 @@ class _ServerEditPageState extends State<ServerEditPage> {
return [ return [
Text(l10n.customCmd, style: UIs.text13Grey), Text(l10n.customCmd, style: UIs.text13Grey),
UIs.height7, UIs.height7,
Input( _customCmds.listenVal(
controller: _customCmdCtrl, (vals) {
type: TextInputType.text, return ListTile(
maxLines: 3, leading: const Icon(BoxIcons.bxs_file_json).paddingOnly(left: 10),
label: 'JSON', title: const Text('JSON'),
icon: Icons.code, subtitle: vals.isEmpty
hint: '{${l10n.customCmdHint}}', ? null
), : Text(vals.keys.join(','), style: UIs.textGrey),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () async {
final res = await KvEditor.route.go(
context,
args: KvEditorArgs(data: _customCmds.value),
);
if (res == null) return;
_customCmds.value = res;
},
);
},
).cardx,
ListTile( ListTile(
leading: const Padding( leading: const Padding(
padding: EdgeInsets.only(left: 10), padding: EdgeInsets.only(left: 10),
@@ -486,9 +458,10 @@ class _ServerEditPageState extends State<ServerEditPage> {
Input( Input(
controller: _wolMacCtrl, controller: _wolMacCtrl,
type: TextInputType.text, type: TextInputType.text,
label: 'Mac ${l10n.addr}', label: 'MAC ${l10n.addr}',
icon: Icons.computer, icon: Icons.computer,
hint: '00:11:22:33:44:55', hint: '00:11:22:33:44:55',
suggestion: false,
), ),
Input( Input(
controller: _wolIpCtrl, controller: _wolIpCtrl,
@@ -496,6 +469,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: 'IP ${l10n.addr}', label: 'IP ${l10n.addr}',
icon: Icons.network_cell, icon: Icons.network_cell,
hint: '192.168.1.x', hint: '192.168.1.x',
suggestion: false,
), ),
Input( Input(
controller: _wolPwdCtrl, controller: _wolPwdCtrl,
@@ -504,6 +478,7 @@ class _ServerEditPageState extends State<ServerEditPage> {
label: l10n.pwd, label: l10n.pwd,
icon: Icons.password, icon: Icons.password,
hint: l10n.pwd, hint: l10n.pwd,
suggestion: false,
), ),
]; ];
} }
@@ -595,19 +570,11 @@ class _ServerEditPageState extends State<ServerEditPage> {
if (_portController.text.isEmpty) { if (_portController.text.isEmpty) {
_portController.text = '22'; _portController.text = '22';
} }
final customCmds = () { final customCmds = _customCmds.value;
if (_customCmdCtrl.text.isEmpty) return null;
try {
return json.decode(_customCmdCtrl.text).cast<String, String>();
} catch (e) {
context.showSnackBar(l10n.invalidJson);
return null;
}
}();
final custom = ServerCustom( final custom = ServerCustom(
pveAddr: _pveAddrCtrl.text.selfIfNotNullEmpty, pveAddr: _pveAddrCtrl.text.selfIfNotNullEmpty,
pveIgnoreCert: _pveIgnoreCert.value, pveIgnoreCert: _pveIgnoreCert.value,
cmds: customCmds, cmds: customCmds.isEmpty ? null : customCmds,
preferTempDev: _preferTempDevCtrl.text.selfIfNotNullEmpty, preferTempDev: _preferTempDevCtrl.text.selfIfNotNullEmpty,
logoUrl: _logoUrlCtrl.text.selfIfNotNullEmpty, logoUrl: _logoUrlCtrl.text.selfIfNotNullEmpty,
); );
@@ -647,19 +614,9 @@ class _ServerEditPageState extends State<ServerEditPage> {
jumpId: _jumpServer.value, jumpId: _jumpServer.value,
custom: custom, custom: custom,
wolCfg: wol, wolCfg: wol,
envs: _env.value.isEmpty ? null : _env.value,
); );
final tipShown = Stores.history.writeScriptTipShown;
if (!tipShown.fetch()) {
final ok = await context.showRoundDialog(
title: l10n.attention,
child: SimpleMarkdown(data: l10n.beforeConnect(Urls.thisRepo)),
actions: Btns.oks(onTap: () => context.pop(true)),
);
if (ok != true) return;
tipShown.put(true);
}
if (widget.spi == null) { if (widget.spi == null) {
Pros.server.addServer(spi); Pros.server.addServer(spi);
} else { } else {
@@ -668,4 +625,70 @@ class _ServerEditPageState extends State<ServerEditPage> {
context.pop(); context.pop();
} }
Widget _buildWriteScriptTip() {
return Center(
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () {
context.showRoundDialog(
title: l10n.attention,
child: SimpleMarkdown(data: l10n.writeScriptTip),
actions: Btns.oks(onTap: () => context.pop(true)),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.tips_and_updates, size: 15, color: Colors.grey),
UIs.width13,
Text(l10n.attention, style: UIs.textGrey)
],
).paddingSymmetric(horizontal: 13, vertical: 3),
),
).paddingOnly(bottom: 13);
}
@override
void afterFirstLayout(BuildContext context) {
final spi = widget.spi;
if (spi != null) {
_nameController.text = spi.name;
_ipController.text = spi.ip;
_portController.text = spi.port.toString();
_usernameController.text = spi.user;
if (spi.keyId == null) {
_passwordController.text = spi.pwd ?? '';
} else {
_keyIdx.value = Pros.key.pkis.indexWhere(
(e) => e.id == widget.spi!.keyId,
);
}
/// List in dart is passed by pointer, so you need to copy it here
_tags.addAll(spi.tags ?? []);
_altUrlController.text = spi.alterUrl ?? '';
_autoConnect.value = spi.autoConnect ?? true;
_jumpServer.value = spi.jumpId;
final custom = spi.custom;
if (custom != null) {
_pveAddrCtrl.text = custom.pveAddr ?? '';
_pveIgnoreCert.value = custom.pveIgnoreCert;
_customCmds.value = custom.cmds ?? {};
_preferTempDevCtrl.text = custom.preferTempDev ?? '';
_logoUrlCtrl.text = custom.logoUrl ?? '';
}
final wol = spi.wolCfg;
if (wol != null) {
_wolMacCtrl.text = wol.mac;
_wolIpCtrl.text = wol.ip;
_wolPwdCtrl.text = wol.pwd ?? '';
}
_env.value = spi.envs ?? {};
}
}
} }

View File

@@ -27,6 +27,9 @@ class ServerPage extends StatefulWidget {
State<ServerPage> createState() => _ServerPageState(); State<ServerPage> createState() => _ServerPageState();
} }
const _cardPad = 74.0;
const _cardPadSingle = 13.0;
class _ServerPageState extends State<ServerPage> class _ServerPageState extends State<ServerPage>
with AutomaticKeepAliveClientMixin, AfterLayoutMixin { with AutomaticKeepAliveClientMixin, AfterLayoutMixin {
late MediaQueryData _media; late MediaQueryData _media;
@@ -97,7 +100,7 @@ class _ServerPageState extends State<ServerPage>
), ),
floatingActionButton: AutoHide( floatingActionButton: AutoHide(
key: _autoHideKey, key: _autoHideKey,
direction: AxisDirection.down, direction: AxisDirection.right,
offset: 75, offset: 75,
controller: _scrollController, controller: _scrollController,
child: FloatingActionButton( child: FloatingActionButton(
@@ -289,7 +292,12 @@ class _ServerPageState extends State<ServerPage>
} }
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 7), padding: const EdgeInsets.only(
left: _cardPadSingle,
right: 3,
top: _cardPadSingle,
bottom: _cardPadSingle,
),
child: _buildRealServerCard(srv), child: _buildRealServerCard(srv),
), ),
), ),
@@ -299,7 +307,7 @@ class _ServerPageState extends State<ServerPage>
/// The child's width mat not equal to 1/4 of the screen width, /// The child's width mat not equal to 1/4 of the screen width,
/// so we need to wrap it with a SizedBox. /// so we need to wrap it with a SizedBox.
Widget _wrapWithSizedbox(Widget child, [bool circle = false]) { Widget _wrapWithSizedbox(Widget child, [bool circle = false]) {
var width = (_media.size.width - 74) / (circle ? 4 : 4.3); var width = (_media.size.width - _cardPad) / (circle ? 4 : 4.3);
if (_useDoubleColumn) width /= 2; if (_useDoubleColumn) width /= 2;
return SizedBox( return SizedBox(
width: width, width: width,
@@ -340,66 +348,72 @@ class _ServerPageState extends State<ServerPage>
} }
List<Widget> _buildFlippedCard(Server srv) { List<Widget> _buildFlippedCard(Server srv) {
final children = [
IconTextBtn(
onPressed: () => _askFor(
func: () async {
if (Stores.setting.showSuspendTip.fetch()) {
await context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.suspendTip),
);
Stores.setting.showSuspendTip.put(false);
}
srv.client?.execWithPwd(
ShellFunc.suspend.exec,
context: context,
id: srv.id,
);
},
typ: l10n.suspend,
name: srv.spi.name,
),
icon: Icons.stop,
text: l10n.suspend,
),
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.shutdown.exec,
context: context,
id: srv.id,
),
typ: l10n.shutdown,
name: srv.spi.name,
),
icon: Icons.power_off,
text: l10n.shutdown,
),
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.reboot.exec,
context: context,
id: srv.id,
),
typ: l10n.reboot,
name: srv.spi.name,
),
icon: Icons.restart_alt,
text: l10n.reboot,
),
IconTextBtn(
onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context),
icon: Icons.edit,
text: l10n.edit,
)
];
final width = (_media.size.width - _cardPad) / children.length;
return [ return [
UIs.height13, UIs.height13,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: children.map((e) {
IconTextBtn( if (width == 0) return e;
onPressed: () => _askFor( return SizedBox(width: width, child: e);
func: () async { }).toList(),
if (Stores.setting.showSuspendTip.fetch()) { ),
await context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.suspendTip),
);
Stores.setting.showSuspendTip.put(false);
}
srv.client?.execWithPwd(
ShellFunc.suspend.exec,
context: context,
id: srv.id,
);
},
typ: l10n.suspend,
name: srv.spi.name,
),
icon: Icons.stop,
text: l10n.suspend,
),
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.shutdown.exec,
context: context,
id: srv.id,
),
typ: l10n.shutdown,
name: srv.spi.name,
),
icon: Icons.power_off,
text: l10n.shutdown,
),
IconTextBtn(
onPressed: () => _askFor(
func: () => srv.client?.execWithPwd(
ShellFunc.reboot.exec,
context: context,
id: srv.id,
),
typ: l10n.reboot,
name: srv.spi.name,
),
icon: Icons.restart_alt,
text: l10n.reboot,
),
IconTextBtn(
onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context),
icon: Icons.edit,
text: l10n.edit,
)
],
)
]; ];
} }
@@ -458,13 +472,9 @@ class _ServerPageState extends State<ServerPage>
} }
Widget _buildTopRightWidget(Server s) { Widget _buildTopRightWidget(Server s) {
return switch (s.conn) { final (child, onTap) = switch (s.conn) {
ServerConn.connecting || ServerConn.connecting || ServerConn.loading || ServerConn.connected => (
ServerConn.loading || SizedBox(
ServerConn.connected =>
Padding(
padding: const EdgeInsets.only(left: 11, right: 3),
child: SizedBox(
width: 19, width: 19,
height: 19, height: 19,
child: CircularProgressIndicator( child: CircularProgressIndicator(
@@ -472,46 +482,55 @@ class _ServerPageState extends State<ServerPage>
valueColor: AlwaysStoppedAnimation(UIs.primaryColor), valueColor: AlwaysStoppedAnimation(UIs.primaryColor),
), ),
), ),
null,
), ),
ServerConn.failed => InkWell( ServerConn.failed => (
onTap: () { const Icon(
Icons.refresh,
size: 21,
color: Colors.grey,
),
() {
TryLimiter.reset(s.spi.id); TryLimiter.reset(s.spi.id);
Pros.server.refresh(spi: s.spi); Pros.server.refresh(spi: s.spi);
}, },
child: const Padding(
padding: EdgeInsets.only(left: 11, right: 3),
child: Icon(
Icons.refresh,
size: 21,
color: Colors.grey,
),
),
), ),
ServerConn.disconnected => InkWell( ServerConn.disconnected => (
onTap: () => Pros.server.refresh(spi: s.spi), const Icon(
child: const Padding( BoxIcons.bx_link,
padding: EdgeInsets.only(left: 11, right: 3), size: 21,
child: Icon( color: Colors.grey,
BoxIcons.bx_link,
size: 21,
color: Colors.grey,
),
), ),
() => Pros.server.refresh(spi: s.spi)
), ),
ServerConn.finished => InkWell( ServerConn.finished => (
onTap: () => Pros.server.closeServer(id: s.spi.id), const Icon(
child: const Padding( BoxIcons.bx_unlink,
padding: EdgeInsets.only(left: 11, right: 3), size: 17,
child: Icon( color: Colors.grey,
BoxIcons.bx_unlink,
size: 17,
color: Colors.grey,
),
), ),
() => Pros.server.closeServer(id: s.spi.id),
),
_ when Stores.setting.serverTabUseOldUI.fetch() => (
ServerFuncBtnsTopRight(spi: s.spi),
null,
), ),
_ when Stores.setting.serverTabUseOldUI.fetch() =>
ServerFuncBtnsTopRight(spi: s.spi),
}; };
// Or the loading icon will be rescaled.
final wrapped = child is SizedBox
? child
: SizedBox(
height: _kCardHeightMin,
width: 27,
child: child,
);
if (onTap == null) return wrapped.paddingOnly(left: 10);
return InkWell(
borderRadius: BorderRadius.circular(7),
onTap: onTap,
child: wrapped,
).paddingOnly(left: 10);
} }
Widget _buildTopRightText(Server s) { Widget _buildTopRightText(Server s) {
@@ -646,20 +665,25 @@ ${ss.err?.message ?? l10n.unknownError}
_tag == null || (pro.pick(id: e)?.spi.tags?.contains(_tag) ?? false)) _tag == null || (pro.pick(id: e)?.spi.tags?.contains(_tag) ?? false))
.toList(); .toList();
static const _kCardHeightMin = 23.0;
static const _kCardHeightFlip = 99.0;
static const _kCardHeightNormal = 108.0;
static const _kCardHeightMoveOutFuncs = 135.0;
double? _calcCardHeight(ServerConn cs, bool flip) { double? _calcCardHeight(ServerConn cs, bool flip) {
if (_textFactorDouble != 1.0) return null; if (_textFactorDouble != 1.0) return null;
if (cs != ServerConn.finished) { if (cs != ServerConn.finished) {
return 23.0; return _kCardHeightMin;
} }
if (flip) { if (flip) {
return 97.0; return _kCardHeightFlip;
} }
if (Stores.setting.moveOutServerTabFuncBtns.fetch() && if (Stores.setting.moveOutServerTabFuncBtns.fetch() &&
// Discussion #146 // Discussion #146
!Stores.setting.serverTabUseOldUI.fetch()) { !Stores.setting.serverTabUseOldUI.fetch()) {
return 132; return _kCardHeightMoveOutFuncs;
} }
return 106; return _kCardHeightNormal;
} }
void _askFor({ void _askFor({

View File

@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_highlight/theme_map.dart'; import 'package:flutter_highlight/theme_map.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:provider/provider.dart'; import 'package:locale_names/locale_names.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/rebuild.dart'; import 'package:server_box/data/res/rebuild.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
@@ -14,7 +14,6 @@ import 'package:server_box/view/page/setting/platform/platform_pub.dart';
import '../../../core/route.dart'; import '../../../core/route.dart';
import '../../../data/model/app/net_view.dart'; import '../../../data/model/app/net_view.dart';
import '../../../data/provider/app.dart';
import '../../../data/res/build_data.dart'; import '../../../data/res/build_data.dart';
const _kIconSize = 23.0; const _kIconSize = 23.0;
@@ -156,6 +155,7 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildSSH() { Widget _buildSSH() {
return Column( return Column(
children: [ children: [
_buildLetterCache(),
_buildSSHWakeLock(), _buildSSHWakeLock(),
_buildTermTheme(), _buildTermTheme(),
_buildFont(), _buildFont(),
@@ -183,12 +183,13 @@ class _SettingPageState extends State<SettingPage> {
return ListTile( return ListTile(
leading: const Icon(Icons.update), leading: const Icon(Icons.update),
title: Text(l10n.autoCheckUpdate), title: Text(l10n.autoCheckUpdate),
subtitle: Consumer<AppProvider>( subtitle: ValBuilder(
builder: (ctx, app, __) { listenable: AppUpdateIface.newestBuild,
builder: (val) {
String display; String display;
if (app.newestBuild != null) { if (val != null) {
if (app.newestBuild! > BuildData.build) { if (val > BuildData.build) {
display = l10n.versionHaveUpdate(app.newestBuild!); display = l10n.versionHaveUpdate(val);
} else { } else {
display = l10n.versionUpdated(BuildData.build); display = l10n.versionUpdated(BuildData.build);
} }
@@ -270,6 +271,7 @@ class _SettingPageState extends State<SettingPage> {
controller: ctrl, controller: ctrl,
hint: '#8b2252', hint: '#8b2252',
icon: Icons.colorize, icon: Icons.colorize,
suggestion: false,
), ),
ColorPicker( ColorPicker(
color: Color(_setting.primaryColor.fetch()), color: Color(_setting.primaryColor.fetch()),
@@ -305,7 +307,7 @@ class _SettingPageState extends State<SettingPage> {
_setting.primaryColor.put(color.value); _setting.primaryColor.put(color.value);
context.pop(); context.pop();
context.pop(); context.pop();
RNodes.app.build(); RNodes.app.notify();
} }
// Widget _buildLaunchPage() { // Widget _buildLaunchPage() {
@@ -393,7 +395,7 @@ class _SettingPageState extends State<SettingPage> {
); );
if (selected != null) { if (selected != null) {
_setting.themeMode.put(selected); _setting.themeMode.put(selected);
RNodes.app.build(); RNodes.app.notify();
} }
}, },
trailing: ValBuilder( trailing: ValBuilder(
@@ -442,7 +444,7 @@ class _SettingPageState extends State<SettingPage> {
onPressed: () { onPressed: () {
_setting.fontPath.delete(); _setting.fontPath.delete();
context.pop(); context.pop();
RNodes.app.build(); RNodes.app.notify();
}, },
child: Text(l10n.clear), child: Text(l10n.clear),
) )
@@ -466,14 +468,18 @@ class _SettingPageState extends State<SettingPage> {
} }
context.pop(); context.pop();
RNodes.app.build(); RNodes.app.notify();
} }
Widget _buildTermFontSize() { Widget _buildTermFontSize() {
return ListTile( return ListTile(
leading: const Icon(MingCute.font_size_line), leading: const Icon(MingCute.font_size_line),
title: Text(l10n.fontSize), // title: Text(l10n.fontSize),
subtitle: Text(l10n.termFontSizeTip, style: UIs.textGrey), // subtitle: Text(l10n.termFontSizeTip, style: UIs.textGrey),
title: TipText(
tip: l10n.termFontSizeTip,
text: l10n.fontSize,
),
trailing: ValBuilder( trailing: ValBuilder(
listenable: _setting.termFontSize.listenable(), listenable: _setting.termFontSize.listenable(),
builder: (val) => Text( builder: (val) => Text(
@@ -530,13 +536,13 @@ class _SettingPageState extends State<SettingPage> {
final selected = await context.showPickSingleDialog( final selected = await context.showPickSingleDialog(
title: l10n.language, title: l10n.language,
items: AppLocalizations.supportedLocales, items: AppLocalizations.supportedLocales,
name: (p0) => p0.code, name: (p0) => '${p0.nativeDisplayLanguage} (${p0.code})',
initial: _setting.locale.fetch().toLocale, initial: _setting.locale.fetch().toLocale,
); );
if (selected != null) { if (selected != null) {
_setting.locale.put(selected.code); _setting.locale.put(selected.code);
context.pop(); context.pop();
RNodes.app.build(); RNodes.app.notify();
} }
}, },
trailing: ListenBuilder( trailing: ListenBuilder(
@@ -605,11 +611,15 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildFullScreenSwitch() { Widget _buildFullScreenSwitch() {
return ListTile( return ListTile(
leading: const Icon(Bootstrap.phone_landscape_fill), leading: const Icon(Bootstrap.phone_landscape_fill),
title: Text(l10n.fullScreen), // title: Text(l10n.fullScreen),
subtitle: Text(l10n.fullScreenTip, style: UIs.textGrey), // subtitle: Text(l10n.fullScreenTip, style: UIs.textGrey),
title: TipText(
tip: l10n.fullScreenTip,
text: l10n.fullScreen,
),
trailing: StoreSwitch( trailing: StoreSwitch(
prop: _setting.fullScreen, prop: _setting.fullScreen,
callback: (_) => RNodes.app.build(), callback: (_) => RNodes.app.notify(),
), ),
); );
} }
@@ -681,6 +691,7 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildSFTP() { Widget _buildSFTP() {
return Column( return Column(
children: [ children: [
_buildSftpEditor(),
_buildSftpRmrDir(), _buildSftpRmrDir(),
_buildSftpOpenLastPath(), _buildSftpOpenLastPath(),
_buildSftpShowFoldersFirst(), _buildSftpShowFoldersFirst(),
@@ -691,8 +702,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildSftpOpenLastPath() { Widget _buildSftpOpenLastPath() {
return ListTile( return ListTile(
leading: const Icon(MingCute.history_line), leading: const Icon(MingCute.history_line),
title: Text(l10n.openLastPath), // title: Text(l10n.openLastPath),
subtitle: Text(l10n.openLastPathTip, style: UIs.textGrey), // subtitle: Text(l10n.openLastPathTip, style: UIs.textGrey),
title: TipText(
tip: l10n.openLastPathTip,
text: l10n.openLastPath,
),
trailing: StoreSwitch(prop: _setting.sftpOpenLastPath), trailing: StoreSwitch(prop: _setting.sftpOpenLastPath),
); );
} }
@@ -766,8 +781,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildTextScaler() { Widget _buildTextScaler() {
final ctrl = TextEditingController(text: _setting.textFactor.toString()); final ctrl = TextEditingController(text: _setting.textFactor.toString());
return ListTile( return ListTile(
title: Text(l10n.textScaler), // title: Text(l10n.textScaler),
subtitle: Text(l10n.textScalerTip, style: UIs.textGrey), // subtitle: Text(l10n.textScalerTip, style: UIs.textGrey),
title: TipText(
tip: l10n.textScalerTip,
text: l10n.textScaler,
),
trailing: ValBuilder( trailing: ValBuilder(
listenable: _setting.textFactor.listenable(), listenable: _setting.textFactor.listenable(),
builder: (val) => Text( builder: (val) => Text(
@@ -784,6 +803,7 @@ class _SettingPageState extends State<SettingPage> {
icon: Icons.format_size, icon: Icons.format_size,
controller: ctrl, controller: ctrl,
onSubmitted: _onSaveTextScaler, onSubmitted: _onSaveTextScaler,
suggestion: false,
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -802,7 +822,7 @@ class _SettingPageState extends State<SettingPage> {
return; return;
} }
_setting.textFactor.put(val); _setting.textFactor.put(val);
RNodes.app.build(); RNodes.app.notify();
context.pop(); context.pop();
} }
@@ -819,8 +839,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildServerFuncBtnsSwitch() { Widget _buildServerFuncBtnsSwitch() {
return ListTile( return ListTile(
title: Text(l10n.location), // title: Text(l10n.location),
subtitle: Text(l10n.moveOutServerFuncBtnsHelp, style: UIs.text13Grey), // subtitle: Text(l10n.moveOutServerFuncBtnsHelp, style: UIs.text13Grey),
title: TipText(
tip: l10n.moveOutServerFuncBtnsHelp,
text: l10n.location,
),
trailing: StoreSwitch(prop: _setting.moveOutServerTabFuncBtns), trailing: StoreSwitch(prop: _setting.moveOutServerTabFuncBtns),
); );
} }
@@ -888,6 +912,7 @@ class _SettingPageState extends State<SettingPage> {
autoFocus: true, autoFocus: true,
type: TextInputType.number, type: TextInputType.number,
icon: Icons.font_download, icon: Icons.font_download,
suggestion: false,
onSubmitted: (_) => onSave(), onSubmitted: (_) => onSave(),
), ),
actions: [ actions: [
@@ -910,8 +935,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildDoubleColumnServersPage() { Widget _buildDoubleColumnServersPage() {
return ListTile( return ListTile(
title: Text(l10n.doubleColumnMode), // title: Text(l10n.doubleColumnMode),
subtitle: Text(l10n.doubleColumnTip, style: UIs.textGrey), // subtitle: Text(l10n.doubleColumnTip, style: UIs.textGrey),
title: TipText(
tip: l10n.doubleColumnTip,
text: l10n.doubleColumnMode,
),
trailing: StoreSwitch(prop: _setting.doubleColumnServersPage), trailing: StoreSwitch(prop: _setting.doubleColumnServersPage),
); );
} }
@@ -934,8 +963,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildEditorHighlight() { Widget _buildEditorHighlight() {
return ListTile( return ListTile(
leading: const Icon(MingCute.code_line, size: _kIconSize), leading: const Icon(MingCute.code_line, size: _kIconSize),
title: Text(l10n.highlight), // title: Text(l10n.highlight),
subtitle: Text(l10n.editorHighlightTip, style: UIs.textGrey), // subtitle: Text(l10n.editorHighlightTip, style: UIs.textGrey),
title: TipText(
tip: l10n.editorHighlightTip,
text: l10n.highlight,
),
trailing: StoreSwitch(prop: _setting.editorHighlight), trailing: StoreSwitch(prop: _setting.editorHighlight),
); );
} }
@@ -958,9 +991,11 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildContainerTrySudo() { Widget _buildContainerTrySudo() {
return ListTile( return ListTile(
leading: const Icon(Clarity.administrator_solid), leading: const Icon(EvaIcons.person_done),
title: Text(l10n.trySudo), title: TipText(
subtitle: Text(l10n.containerTrySudoTip, style: UIs.textGrey), tip: l10n.containerTrySudoTip,
text: l10n.trySudo,
),
trailing: StoreSwitch(prop: _setting.containerTrySudo), trailing: StoreSwitch(prop: _setting.containerTrySudo),
); );
} }
@@ -975,9 +1010,13 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildContainerParseStat() { Widget _buildContainerParseStat() {
return ListTile( return ListTile(
leading: const Icon(IonIcons.stats_chart, size: _kIconSize), leading: const Icon(MingCute.chart_line_line, size: _kIconSize),
title: Text(l10n.parseContainerStats), // title: Text(l10n.parseContainerStats),
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey), // subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
title: TipText(
tip: l10n.parseContainerStatsTip,
text: l10n.stat,
),
trailing: StoreSwitch(prop: _setting.containerParseStat), trailing: StoreSwitch(prop: _setting.containerParseStat),
); );
} }
@@ -999,8 +1038,12 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildRememberPwdInMem() { Widget _buildRememberPwdInMem() {
return ListTile( return ListTile(
title: Text(l10n.rememberPwdInMem), // title: Text(l10n.rememberPwdInMem),
subtitle: Text(l10n.rememberPwdInMemTip, style: UIs.textGrey), // subtitle: Text(l10n.rememberPwdInMemTip, style: UIs.textGrey),
title: TipText(
tip: l10n.rememberPwdInMemTip,
text: l10n.rememberPwdInMem,
),
trailing: StoreSwitch(prop: _setting.rememberPwdInMem), trailing: StoreSwitch(prop: _setting.rememberPwdInMem),
); );
} }
@@ -1049,7 +1092,7 @@ class _SettingPageState extends State<SettingPage> {
title: Text(l10n.more), title: Text(l10n.more),
children: [ children: [
_buildBeta(), _buildBeta(),
_buildWakeLock(), if (isMobile) _buildWakeLock(),
_buildCollapseUI(), _buildCollapseUI(),
_buildCupertinoRoute(), _buildCupertinoRoute(),
if (isDesktop) _buildHideTitleBar(), if (isDesktop) _buildHideTitleBar(),
@@ -1135,13 +1178,13 @@ class _SettingPageState extends State<SettingPage> {
return ListTile( return ListTile(
leading: const Icon(Icons.image), leading: const Icon(Icons.image),
title: Text('Logo ${l10n.addr}'), title: const Text('Logo URL'),
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () { onTap: () {
final ctrl = final ctrl =
TextEditingController(text: _setting.serverLogoUrl.fetch()); TextEditingController(text: _setting.serverLogoUrl.fetch());
context.showRoundDialog( context.showRoundDialog(
title: 'Logo ${l10n.addr}', title: 'Logo URL',
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -1151,6 +1194,7 @@ class _SettingPageState extends State<SettingPage> {
hint: 'https://example.com/logo.png', hint: 'https://example.com/logo.png',
icon: Icons.link, icon: Icons.link,
maxLines: 2, maxLines: 2,
suggestion: false,
onSubmitted: onSave, onSubmitted: onSave,
), ),
ListTile( ListTile(
@@ -1178,4 +1222,56 @@ class _SettingPageState extends State<SettingPage> {
trailing: StoreSwitch(prop: _setting.betaTest), trailing: StoreSwitch(prop: _setting.betaTest),
); );
} }
Widget _buildLetterCache() {
return ListTile(
leading: const Icon(Bootstrap.alphabet),
// title: Text(l10n.letterCache),
// subtitle: Text(
// '${l10n.letterCacheTip}\n${l10n.needRestart}',
// style: UIs.textGrey,
// ),
title: TipText(
tip: '${l10n.letterCacheTip}\n${l10n.needRestart}',
text: l10n.letterCache,
),
trailing: StoreSwitch(prop: _setting.letterCache),
);
}
Widget _buildSftpEditor() {
return _setting.sftpEditor.listenable().listenVal(
(val) {
return ListTile(
leading: const Icon(MingCute.edit_fill),
title: TipText(text: l10n.editor, tip: l10n.sftpEditorTip),
trailing: Text(
val.isEmpty ? l10n.inner : val,
style: UIs.text15,
),
onTap: () async {
final ctrl = TextEditingController(text: val);
void onSave(String s) {
_setting.sftpEditor.put(s);
context.pop();
}
await context.showRoundDialog<bool>(
title: l10n.choose,
child: Input(
controller: ctrl,
autoFocus: true,
label: l10n.editor,
hint: '\$EDITOR / vim / nano ...',
icon: Icons.edit,
suggestion: false,
onSubmitted: onSave,
),
actions: Btns.oks(onTap: () => onSave(ctrl.text)),
);
},
);
},
);
}
} }

View File

@@ -44,8 +44,9 @@ class _AndroidSettingsPageState extends State<AndroidSettingsPage> {
Widget _buildBgRun() { Widget _buildBgRun() {
return ListTile( return ListTile(
title: Text(l10n.bgRun), // title: Text(l10n.bgRun),
subtitle: Text(l10n.bgRunTip, style: UIs.textGrey), // subtitle: Text(l10n.bgRunTip, style: UIs.textGrey),
title: TipText(text: l10n.bgRun, tip: l10n.bgRunTip),
trailing: StoreSwitch(prop: Stores.setting.bgRun), trailing: StoreSwitch(prop: Stores.setting.bgRun),
); );
} }

View File

@@ -114,13 +114,8 @@ class _IOSSettingsPageState extends State<IOSSettingsPage> {
final result = await AppRoutes.kvEditor(data: urls).go(context); final result = await AppRoutes.kvEditor(data: urls).go(context);
if (result == null || result is! Map<String, String>) return; if (result == null || result is! Map<String, String>) return;
try { await context.showLoadingDialog(fn: () async {
await context.showLoadingDialog(fn: () async { await wc.updateApplicationContext({'urls': result});
await wc.updateApplicationContext({'urls': result}); });
});
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: 'Watch Context');
Loggers.app.warning('Update watch config failed', e, s);
}
} }
} }

View File

@@ -45,7 +45,7 @@ class _ServerDetailOrderPageState extends State<ServerFuncBtnsOrderPage> {
text: TextSpan( text: TextSpan(
children: [ children: [
WidgetSpan(child: Icon(funcBtn.icon)), WidgetSpan(child: Icon(funcBtn.icon)),
const WidgetSpan(child: UIs.width7), const WidgetSpan(child: UIs.width13),
TextSpan(text: funcBtn.toStr, style: UIs.textGrey), TextSpan(text: funcBtn.toStr, style: UIs.textGrey),
], ],
), ),

View File

@@ -114,6 +114,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
onSubmitted: (_) => FocusScope.of(context).requestFocus(_scriptNode), onSubmitted: (_) => FocusScope.of(context).requestFocus(_scriptNode),
label: l10n.name, label: l10n.name,
icon: Icons.info, icon: Icons.info,
suggestion: true,
), ),
Input( Input(
controller: _noteController, controller: _noteController,
@@ -122,6 +123,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
type: TextInputType.multiline, type: TextInputType.multiline,
label: l10n.note, label: l10n.note,
icon: Icons.note, icon: Icons.note,
suggestion: true,
), ),
ValBuilder( ValBuilder(
listenable: _tags, listenable: _tags,
@@ -146,6 +148,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
type: TextInputType.multiline, type: TextInputType.multiline,
label: l10n.snippet, label: l10n.snippet,
icon: Icons.code, icon: Icons.code,
suggestion: false,
), ),
_buildAutoRunOn(), _buildAutoRunOn(),
_buildTip(), _buildTip(),
@@ -172,6 +175,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
: Text( : Text(
subtitle, subtitle,
maxLines: 1, maxLines: 1,
style: UIs.textGrey,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
onTap: () async { onTap: () async {

View File

@@ -129,6 +129,7 @@ class SSHPageState extends State<SSHPage>
} }
Widget _buildBody() { Widget _buildBody() {
final letterCache = Stores.setting.letterCache.fetch();
return SizedBox( return SizedBox(
height: _media.size.height - height: _media.size.height -
_virtKeysHeight - _virtKeysHeight -
@@ -144,8 +145,9 @@ class SSHPageState extends State<SSHPage>
_terminal, _terminal,
key: _termKey, key: _termKey,
controller: _terminalController, controller: _terminalController,
keyboardType: TextInputType.text, keyboardType:
enableSuggestions: true, letterCache ? TextInputType.text : TextInputType.visiblePassword,
enableSuggestions: letterCache,
textStyle: _terminalStyle, textStyle: _terminalStyle,
theme: _terminalTheme, theme: _terminalTheme,
deleteDetection: isMobile, deleteDetection: isMobile,
@@ -391,12 +393,13 @@ class SSHPageState extends State<SSHPage>
width: _terminal.viewWidth, width: _terminal.viewWidth,
height: _terminal.viewHeight, height: _terminal.viewHeight,
), ),
environment: widget.spi.envs,
); );
//_setupDiscontinuityTimer(); //_setupDiscontinuityTimer();
if (session == null) { if (session == null) {
_writeLn('Null session'); _writeLn('Null session, please back and retry\r\n');
return; return;
} }

View File

@@ -1,4 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -89,7 +90,7 @@ class _SSHTabPageState extends State<SSHTabPage>
if (confirm != true) return; if (confirm != true) return;
_tabMap.remove(name); _tabMap.remove(name);
_tabRN.build(); _tabRN.notify();
_pageCtrl.previousPage( _pageCtrl.previousPage(
duration: Durations.medium1, curve: Curves.fastEaseInToSlowEaseOut); duration: Durations.medium1, curve: Curves.fastEaseInToSlowEaseOut);
} }
@@ -108,16 +109,27 @@ class _SSHTabPageState extends State<SSHTabPage>
} }
return GridView.builder( return GridView.builder(
padding: const EdgeInsets.all(7), padding: const EdgeInsets.all(7),
itemBuilder: (_, idx) { itemBuilder: (context, idx) {
final spi = Pros.server.pick(id: pro.serverOrder[idx])?.spi; final spi = Pros.server.pick(id: pro.serverOrder[idx])?.spi;
if (spi == null) return UIs.placeholder; if (spi == null) return UIs.placeholder;
return CardX( return CardX(
child: ListTile( child: InkWell(
contentPadding: const EdgeInsets.only(left: 17, right: 7),
title: Text(spi.name),
trailing: const Icon(Icons.chevron_right),
onTap: () => _onTapInitCard(spi), onTap: () => _onTapInitCard(spi),
).center(), child: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 17, right: 7),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
spi.name,
style: Theme.of(context).textTheme.bodyLarge,
),
const Icon(Icons.chevron_right)
],
),
),
),
); );
}, },
itemCount: pro.servers.length, itemCount: pro.servers.length,
@@ -180,7 +192,7 @@ class _SSHTabPageState extends State<SSHTabPage>
), ),
key: key, key: key,
); );
_tabRN.build(); _tabRN.notify();
// Wait for the page to be built // Wait for the page to be built
await Future.delayed(Durations.short3); await Future.delayed(Durations.short3);
final idx = _tabMap.keys.toList().indexOf(name); final idx = _tabMap.keys.toList().indexOf(name);
@@ -202,7 +214,7 @@ final class _TabBar extends StatelessWidget implements PreferredSizeWidget {
required this.onClose, required this.onClose,
}); });
final ValueNotifier<int> idxVN; final ValueListenable<int> idxVN;
final _TabMap map; final _TabMap map;
final void Function(int idx) onTap; final void Function(int idx) onTap;
final void Function(String name) onClose; final void Function(String name) onClose;

View File

@@ -329,6 +329,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
child: Input( child: Input(
autoFocus: true, autoFocus: true,
controller: TextEditingController(text: fileName), controller: TextEditingController(text: fileName),
suggestion: true,
onSubmitted: (p0) { onSubmitted: (p0) {
context.pop(); context.pop();
final newPath = '${file.parent.path}/$p0'; final newPath = '${file.parent.path}/$p0';

View File

@@ -1,22 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/extension/sftpfile.dart'; import 'package:server_box/core/extension/sftpfile.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/core/utils/comparator.dart'; import 'package:server_box/core/utils/comparator.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/sftp/absolute_path.dart';
import 'package:server_box/data/model/sftp/browser_status.dart';
import 'package:server_box/data/model/sftp/req.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/widget/omit_start_text.dart'; import 'package:server_box/view/widget/omit_start_text.dart';
import '../../../core/route.dart'; import 'package:icons_plus/icons_plus.dart';
import '../../../data/model/server/server_private_info.dart'; import 'package:server_box/view/widget/two_line_text.dart';
import '../../../data/model/sftp/absolute_path.dart'; import 'package:server_box/view/widget/unix_perm.dart';
import '../../../data/model/sftp/browser_status.dart';
import '../../../data/model/sftp/req.dart';
import '../../widget/two_line_text.dart';
class SftpPage extends StatefulWidget { class SftpPage extends StatefulWidget {
final ServerPrivateInfo spi; final ServerPrivateInfo spi;
@@ -174,49 +177,51 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
Widget _buildUploadBtn() { Widget _buildUploadBtn() {
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
final idx = await context.showRoundDialog( final idx = await context.showRoundDialog(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( ListTile(
leading: const Icon(Icons.open_in_new), leading: const Icon(Icons.open_in_new),
title: Text(l10n.system), title: Text(l10n.system),
onTap: () => context.pop(1), onTap: () => context.pop(1),
), ),
ListTile( ListTile(
leading: const Icon(Icons.folder), leading: const Icon(Icons.folder),
title: Text(l10n.inner), title: Text(l10n.inner),
onTap: () => context.pop(0), onTap: () => context.pop(0),
), ),
], ],
)); ));
final path = await () async { final path = await () async {
switch (idx) { switch (idx) {
case 0: case 0:
return await AppRoutes.localStorage(isPickFile: true) return await AppRoutes.localStorage(isPickFile: true)
.go<String>(context); .go<String>(context);
case 1: case 1:
return await Pfs.pickFilePath(); return await Pfs.pickFilePath();
default: default:
return null; return null;
}
}();
if (path == null) {
return;
} }
final remoteDir = _status.path?.path; }();
if (remoteDir == null) { if (path == null) {
context.showSnackBar('remote path is null'); return;
return; }
} final remoteDir = _status.path?.path;
final remotePath = '$remoteDir/${path.split('/').last}'; if (remoteDir == null) {
Loggers.app.info('SFTP upload local: $path, remote: $remotePath'); context.showSnackBar('remote path is null');
Pros.sftp.add( return;
SftpReq(widget.spi, remotePath, path, SftpReqType.upload), }
); final fileName = path.split(Platform.pathSeparator).lastOrNull;
}, final remotePath = '$remoteDir/$fileName';
icon: const Icon(Icons.upload_file)); Loggers.app.info('SFTP upload local: $path, remote: $remotePath');
Pros.sftp.add(
SftpReq(widget.spi, remotePath, path, SftpReqType.upload),
);
},
icon: const Icon(Icons.upload_file),
);
} }
Widget _buildAddBtn() { Widget _buildAddBtn() {
@@ -264,6 +269,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
label: l10n.path, label: l10n.path,
node: node, node: node,
controller: controller, controller: controller,
suggestion: true,
onSubmitted: (value) => context.pop(value), onSubmitted: (value) => context.pop(value),
); );
}, },
@@ -275,7 +281,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
} }
_status.path?.update(p); _status.path?.update(p);
final suc = await _listDir(); final suc = await _listDir() ?? false;
if (suc && Stores.setting.recordHistory.fetch()) { if (suc && Stores.setting.recordHistory.fetch()) {
Stores.history.sftpGoPath.add(p); Stores.history.sftpGoPath.add(p);
} }
@@ -371,6 +377,43 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
title: Text(l10n.rename), title: Text(l10n.rename),
onTap: () => _rename(file), onTap: () => _rename(file),
), ),
ListTile(
leading: const Icon(MingCute.copy_line),
title: Text(l10n.copyPath),
onTap: () {
Pfs.copy(_getRemotePath(file));
context.pop();
context.showSnackBar(l10n.success);
},
),
ListTile(
leading: const Icon(Icons.security),
title: Text(l10n.permission),
onTap: () async {
context.pop();
final perm = file.attr.mode?.toUnixPerm() ?? UnixPerm.empty;
var newPerm = perm.copyWith();
final ok = await context.showRoundDialog(
child: UnixPermEditor(perm: perm, onChanged: (p) => newPerm = p),
actions: Btns.oks(onTap: () => context.pop(true)),
);
final permStr = newPerm.perm;
if (ok == true && permStr != perm.perm) {
await context.showLoadingDialog(
fn: () async {
await _client!.run('chmod $permStr "${_getRemotePath(file)}"');
await _listDir();
},
onErr: (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.permission);
return false;
},
);
}
},
),
]; ];
if (notDir) { if (notDir) {
children.addAll([ children.addAll([
@@ -402,6 +445,18 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
} }
Future<void> _edit(SftpName name) async { Future<void> _edit(SftpName name) async {
context.pop();
// #489
final editor = Stores.setting.sftpEditor.fetch();
if (editor.isNotEmpty) {
// Use single quote to avoid escape
final cmd = "$editor '${_getRemotePath(name)}'";
await AppRoutes.ssh(spi: widget.spi, initCmd: cmd).go(context);
await _listDir();
return;
}
final size = name.attr.size; final size = name.attr.size;
if (size == null || size > Miscs.editorMaxSize) { if (size == null || size > Miscs.editorMaxSize) {
context.showSnackBar(l10n.fileTooLarge( context.showSnackBar(l10n.fileTooLarge(
@@ -411,7 +466,6 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
)); ));
return; return;
} }
context.pop();
final remotePath = _getRemotePath(name); final remotePath = _getRemotePath(name);
final localPath = await _getLocalPath(remotePath); final localPath = await _getLocalPath(remotePath);
@@ -423,7 +477,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
SftpReqType.download, SftpReqType.download,
); );
Pros.sftp.add(req, completer: completer); Pros.sftp.add(req, completer: completer);
await context.showLoadingDialog(fn: () => completer.future); final suc = await context.showLoadingDialog(fn: () => completer.future);
if (suc == null) return;
final result = await AppRoutes.editor(path: localPath).go<bool>(context); final result = await AppRoutes.editor(path: localPath).go<bool>(context);
if (result != null && result) { if (result != null && result) {
@@ -514,24 +569,23 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
context.pop(); context.pop();
try {
await context.showLoadingDialog( final suc = await context.showLoadingDialog(
fn: () async { fn: () async {
final remotePath = _getRemotePath(file); final remotePath = _getRemotePath(file);
if (useRmr) { if (useRmr) {
await _client!.run('rm -r "$remotePath"'); await _client!.run('rm -r "$remotePath"');
} else if (file.attr.isDirectory) { } else if (file.attr.isDirectory) {
await _status.client!.rmdir(remotePath); await _status.client!.rmdir(remotePath);
} else { } else {
await _status.client!.remove(remotePath); await _status.client!.remove(remotePath);
} }
}, return true;
onErr: (e, s) {}, },
); );
_listDir(); if (suc == null) return;
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.delete); _listDir();
}
}, },
child: Text(l10n.delete, style: UIs.textRed), child: Text(l10n.delete, style: UIs.textRed),
), ),
@@ -542,6 +596,34 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _mkdir() { void _mkdir() {
context.pop(); context.pop();
final textController = TextEditingController(); final textController = TextEditingController();
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
final suc = await context.showLoadingDialog(
fn: () async {
final dir = '${_status.path!.path}/${textController.text}';
await _status.client!.mkdir(dir);
return true;
},
);
if (suc == null) return;
_listDir();
}
context.showRoundDialog( context.showRoundDialog(
title: l10n.createFolder, title: l10n.createFolder,
child: Input( child: Input(
@@ -549,6 +631,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.folder, icon: Icons.folder,
controller: textController, controller: textController,
label: l10n.name, label: l10n.name,
suggestion: true,
onSubmitted: (_) => onSubmitted(),
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -556,33 +640,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
child: Text(l10n.cancel), child: Text(l10n.cancel),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: onSubmitted,
if (textController.text.isEmpty) {
context.showRoundDialog(
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final dir = '${_status.path!.path}/${textController.text}';
await _status.client!.mkdir(dir);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFolder);
}
},
child: Text(l10n.ok, style: UIs.textRed), child: Text(l10n.ok, style: UIs.textRed),
), ),
], ],
@@ -592,6 +650,35 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _newFile() { void _newFile() {
context.pop(); context.pop();
final textController = TextEditingController(); final textController = TextEditingController();
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
final suc = await context.showLoadingDialog(
fn: () async {
final path = '${_status.path!.path}/${textController.text}';
await _client!.run('touch "$path"');
return true;
},
);
if (suc == null) return;
_listDir();
}
context.showRoundDialog( context.showRoundDialog(
title: l10n.createFile, title: l10n.createFile,
child: Input( child: Input(
@@ -599,37 +686,12 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.insert_drive_file, icon: Icons.insert_drive_file,
controller: textController, controller: textController,
label: l10n.name, label: l10n.name,
suggestion: true,
onSubmitted: (_) => onSubmitted(),
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: onSubmitted,
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final path = '${_status.path!.path}/${textController.text}';
await _client!.run('touch "$path"');
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.createFile);
}
},
child: Text(l10n.ok, style: UIs.textRed), child: Text(l10n.ok, style: UIs.textRed),
), ),
], ],
@@ -639,6 +701,35 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
void _rename(SftpName file) { void _rename(SftpName file) {
context.pop(); context.pop();
final textController = TextEditingController(text: file.filename); final textController = TextEditingController(text: file.filename);
void onSubmitted() async {
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
final suc = await context.showLoadingDialog(
fn: () async {
final newName = textController.text;
await _status.client?.rename(file.filename, newName);
return true;
},
);
if (suc == null) return;
_listDir();
}
context.showRoundDialog( context.showRoundDialog(
title: l10n.rename, title: l10n.rename,
child: Input( child: Input(
@@ -646,38 +737,13 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
icon: Icons.abc, icon: Icons.abc,
controller: textController, controller: textController,
label: l10n.name, label: l10n.name,
suggestion: true,
onSubmitted: (_) => onSubmitted(),
), ),
actions: [ actions: [
TextButton(onPressed: () => context.pop(), child: Text(l10n.cancel)), TextButton(onPressed: () => context.pop(), child: Text(l10n.cancel)),
TextButton( TextButton(
onPressed: () async { onPressed: onSubmitted,
if (textController.text.isEmpty) {
context.showRoundDialog(
title: l10n.attention,
child: Text(l10n.fieldMustNotEmpty),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.ok),
),
],
);
return;
}
context.pop();
try {
await context.showLoadingDialog(
fn: () async {
final newName = textController.text;
await _status.client?.rename(file.filename, newName);
},
onErr: (e, s) {},
);
_listDir();
} catch (e, s) {
context.showErrDialog(e: e, s: s, operation: l10n.rename);
}
},
child: Text(l10n.rename, style: UIs.textRed), child: Text(l10n.rename, style: UIs.textRed),
), ),
], ],
@@ -701,7 +767,10 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
); );
return; return;
} }
await context.showLoadingDialog(fn: () async => _client?.run(cmd)); final suc = await context.showLoadingDialog(
fn: () => _client?.run(cmd) ?? Future.value(false),
);
if (suc == null) return;
_listDir(); _listDir();
} }
@@ -716,7 +785,7 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
} }
/// Only return true if the path is changed /// Only return true if the path is changed
Future<bool> _listDir() async { Future<bool?> _listDir() async {
return context.showLoadingDialog( return context.showLoadingDialog(
fn: () async { fn: () async {
_status.client ??= await _client?.sftp(); _status.client ??= await _client?.sftp();

View File

@@ -3,19 +3,14 @@ import 'dart:io';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/data/model/app/menu/base.dart'; import 'package:server_box/data/model/app/menu/base.dart';
import 'package:server_box/data/model/app/menu/server_func.dart'; import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/pkg/manager.dart';
import 'package:server_box/data/model/server/dist.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:server_box/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import '../../core/route.dart'; import '../../core/route.dart';
import '../../core/utils/server.dart'; import '../../core/utils/server.dart';
import '../../data/model/pkg/upgrade_info.dart';
import '../../data/model/server/server_private_info.dart'; import '../../data/model/server/server_private_info.dart';
class ServerFuncBtnsTopRight extends StatelessWidget { class ServerFuncBtnsTopRight extends StatelessWidget {
@@ -98,9 +93,9 @@ void _onTapMoreBtns(
BuildContext context, BuildContext context,
) async { ) async {
switch (value) { switch (value) {
case ServerFuncBtn.pkg: // case ServerFuncBtn.pkg:
_onPkg(context, spi); // _onPkg(context, spi);
break; // break;
case ServerFuncBtn.sftp: case ServerFuncBtn.sftp:
AppRoutes.sftp(spi: spi).checkGo( AppRoutes.sftp(spi: spi).checkGo(
context: context, context: context,
@@ -225,80 +220,86 @@ bool _checkClient(BuildContext context, String id) {
return true; return true;
} }
Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async { // Future<void> _onPkg(BuildContext context, ServerPrivateInfo spi) async {
final server = spi.server; // final server = spi.server;
final client = server?.client; // final client = server?.client;
if (server == null || client == null) { // if (server == null || client == null) {
context.showSnackBar(l10n.noClient); // context.showSnackBar(l10n.noClient);
return; // return;
} // }
final sys = server.status.more[StatusCmdType.sys]; // final sys = server.status.more[StatusCmdType.sys];
if (sys == null) { // if (sys == null) {
context.showSnackBar(l10n.noResult); // context.showSnackBar(l10n.noResult);
return; // return;
} // }
final pkg = PkgManager.fromDist(sys.dist); // final pkg = PkgManager.fromDist(sys.dist);
if (pkg == null) { // if (pkg == null) {
context.showSnackBar('Unsupported dist: $sys'); // context.showSnackBar('Unsupported dist: $sys');
return; // return;
} // }
// Update pkg list // // Update pkg list
await context.showLoadingDialog( // final suc = await context.showLoadingDialog(
fn: () async { // fn: () async {
final updateCmd = pkg.update; // final updateCmd = pkg.update;
if (updateCmd != null) { // if (updateCmd != null) {
await client.execWithPwd( // await client.execWithPwd(
updateCmd, // updateCmd,
context: context, // context: context,
id: spi.id, // id: spi.id,
); // );
} // }
}, // },
barrierDismiss: true, // barrierDismiss: true,
); // );
// if (suc != true) return;
final listCmd = pkg.listUpdate; // final listCmd = pkg.listUpdate;
if (listCmd == null) { // if (listCmd == null) {
context.showSnackBar('Unsupported dist: $sys'); // context.showSnackBar('Unsupported dist: $sys');
return; // return;
} // }
// Get upgrade list // // Get upgrade list
final result = await context.showLoadingDialog(fn: () async { // final result = await context.showLoadingDialog(
return await client.run(listCmd).string; // fn: () => client.run(listCmd).string,
}); // );
final list = pkg.updateListRemoveUnused(result.split('\n')); // if (result == null || result.isEmpty) {
final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList(); // context.showSnackBar(l10n.noResult);
if (upgradeable.isEmpty) { // return;
context.showSnackBar(l10n.noUpdateAvailable); // }
return;
}
final args = upgradeable.map((e) => e.package).join(' ');
final isSU = server.spi.user == 'root';
final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}';
// Confirm upgrade // final list = pkg.updateListRemoveUnused(result.split('\n'));
final gotoUpgrade = await context.showRoundDialog<bool>( // final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList();
title: l10n.attention, // if (upgradeable.isEmpty) {
child: SingleChildScrollView( // context.showSnackBar(l10n.noUpdateAvailable);
child: Text( // return;
'${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'), // }
), // final args = upgradeable.map((e) => e.package).join(' ');
actions: [ // final isSU = server.spi.user == 'root';
CountDownBtn( // final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}';
onTap: () => context.pop(true),
text: l10n.update,
afterColor: Colors.red,
),
],
);
if (gotoUpgrade != true) return; // // Confirm upgrade
// final gotoUpgrade = await context.showRoundDialog<bool>(
// title: l10n.attention,
// child: SingleChildScrollView(
// child: Text(
// '${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'),
// ),
// actions: [
// CountDownBtn(
// onTap: () => context.pop(true),
// text: l10n.update,
// afterColor: Colors.red,
// ),
// ],
// );
AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo( // if (gotoUpgrade != true) return;
context: context,
check: () => _checkClient(context, spi.id), // AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo(
); // context: context,
} // check: () => _checkClient(context, spi.id),
// );
// }

View File

@@ -0,0 +1,128 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
final class RWX {
final bool r;
final bool w;
final bool x;
const RWX({
required this.r,
required this.w,
required this.x,
});
RWX copyWith({bool? r, bool? w, bool? x}) {
return RWX(r: r ?? this.r, w: w ?? this.w, x: x ?? this.x);
}
int get value {
return (r ? 4 : 0) + (w ? 2 : 0) + (x ? 1 : 0);
}
}
final class UnixPerm {
final RWX user;
final RWX group;
final RWX other;
const UnixPerm({
required this.user,
required this.group,
required this.other,
});
UnixPerm copyWith({RWX? user, RWX? group, RWX? other}) {
return UnixPerm(
user: user ?? this.user,
group: group ?? this.group,
other: other ?? this.other,
);
}
/// eg.: 744
String get perm {
return '${user.value}${group.value}${other.value}';
}
static UnixPerm get empty => const UnixPerm(
user: RWX(r: false, w: false, x: false),
group: RWX(r: false, w: false, x: false),
other: RWX(r: false, w: false, x: false),
);
}
final class UnixPermEditor extends StatefulWidget {
final UnixPerm perm;
final void Function(UnixPerm) onChanged;
const UnixPermEditor(
{super.key, required this.perm, required this.onChanged});
@override
_UnixPermEditorState createState() => _UnixPermEditorState();
}
final class _UnixPermEditorState extends State<UnixPermEditor> {
late UnixPerm perm;
@override
void initState() {
super.initState();
perm = widget.perm;
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('R'),
Text('W'),
Text('X'),
],
).paddingOnly(left: 13),
UIs.height7,
_buildRow('U', perm.user),
_buildRow('G', perm.group),
_buildRow('O', perm.other),
],
);
}
Widget _buildRow(String title, RWX rwx) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 7, child: Text(title)),
_buildSwitch(rwx.r, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(r: v));
});
widget.onChanged(perm);
}),
_buildSwitch(rwx.w, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(w: v));
});
widget.onChanged(perm);
}),
_buildSwitch(rwx.x, (v) {
setState(() {
perm = perm.copyWith(user: rwx.copyWith(x: v));
});
widget.onChanged(perm);
}),
],
);
}
Widget _buildSwitch(bool value, void Function(bool) onChanged) {
return Switch(
value: value,
onChanged: onChanged,
);
}
}

View File

@@ -1,11 +1,19 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "ServerBox") set(BINARY_NAME "ServerBox")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "tech.lolli.toolbox") set(APPLICATION_ID "tech.lolli.toolbox")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW) cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building. # Root filesystem for cross-building.
@@ -18,7 +26,7 @@ if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif() endif()
# Configure build options. # Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE) STRING "Flutter build mode" FORCE)
@@ -27,6 +35,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
endif() endif()
# Compilation settings that should be applied to most targets. # Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET) function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE -Wall -Werror)
@@ -34,9 +46,8 @@ function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction() endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules. # Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR}) add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies. # System-level dependencies.
@@ -45,16 +56,27 @@ pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Application build # Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} add_executable(${BINARY_NAME}
"main.cc" "main.cc"
"my_application.cc" "my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
) )
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME}) apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble) add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch # Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid # correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of # people trying to run the unbundled copy, put it in a subdirectory instead of
@@ -64,6 +86,7 @@ set_target_properties(${BINARY_NAME}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
) )
# Generated plugin build rules, which manage building the plugins and adding # Generated plugin build rules, which manage building the plugins and adding
# them to the application. # them to the application.
include(flutter/generated_plugins.cmake) include(flutter/generated_plugins.cmake)
@@ -94,11 +117,17 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endif() endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.

View File

@@ -1,3 +1,4 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")

View File

@@ -20,6 +20,33 @@ static void my_application_activate(GApplication* application) {
GtkWindow* window = GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "ServerBox");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "ServerBox");
}
gtk_window_set_default_size(window, 400, 777); gtk_window_set_default_size(window, 400, 777);
gtk_widget_show(GTK_WIDGET(window)); gtk_widget_show(GTK_WIDGET(window));
@@ -54,6 +81,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch
return TRUE; return TRUE;
} }
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose. // Implements GObject::dispose.
static void my_application_dispose(GObject* object) { static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object); MyApplication* self = MY_APPLICATION(object);
@@ -64,6 +109,8 @@ static void my_application_dispose(GObject* object) {
static void my_application_class_init(MyApplicationClass* klass) { static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose; G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
} }

View File

@@ -471,7 +471,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -481,7 +481,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -608,7 +608,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -618,7 +618,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -638,7 +638,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 992; CURRENT_PROJECT_VERSION = 1034;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6; "DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -649,7 +649,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.992; MARKETING_VERSION = 1.0.1034;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -368,8 +368,8 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
path: "." path: "."
ref: "v1.0.30" ref: "v1.0.35"
resolved-ref: "30d527ddc7f25c9e1fd7aa5f90df1a9c928ed306" resolved-ref: "7964acfe55e3e3f5d5232a0c2371cff5fa7edc4c"
url: "https://github.com/lppcg/fl_build.git" url: "https://github.com/lppcg/fl_build.git"
source: git source: git
version: "1.0.0" version: "1.0.0"
@@ -385,8 +385,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.50" ref: "v1.0.89"
resolved-ref: fc4e847cc0513157b6ac77e9e82ab57edbdc9482 resolved-ref: "2ea7a87e7f4c1bd68902557799a4e9406e559dcf"
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"
@@ -662,10 +662,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.1" version: "0.6.7"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@@ -754,6 +754,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.10" version: "1.0.10"
locale_names:
dependency: "direct main"
description:
name: locale_names
sha256: "7a89ca54072f4f13d0f5df5a9ba69337554bf2fd057d1dd2a238898f3f159374"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -1434,14 +1442,14 @@ packages:
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
webdav_client: webdav_client:
dependency: transitive dependency: "direct main"
description: description:
path: "." path: "."
ref: main ref: "v1.0.66"
resolved-ref: "88de97ad783c624d81b0dbcc09927b10aabd5580" resolved-ref: "1908cd0f4909730d9ae4d4fc4c05fb2576b3f674"
url: "https://github.com/lollipopkit/webdav_client" url: "https://github.com/lollipopkit/webdav_client"
source: git source: git
version: "1.2.1" version: "1.2.2"
win32: win32:
dependency: transitive dependency: transitive
description: description:

View File

@@ -1,7 +1,7 @@
name: server_box name: server_box
description: server status & toolbox app. description: server status & toolbox app.
publish_to: 'none' publish_to: 'none'
version: 1.0.992+992 version: 1.0.1034+1034
environment: environment:
sdk: ">=3.0.0" sdk: ">=3.0.0"
@@ -21,7 +21,6 @@ dependencies:
code_text_field: ^1.1.0 code_text_field: ^1.1.0
shared_preferences: ^2.1.1 shared_preferences: ^2.1.1
dynamic_color: ^1.6.6 dynamic_color: ^1.6.6
#flutter_secure_storage: ^9.0.0
xml: ^6.4.2 # for parsing nvidia-smi xml: ^6.4.2 # for parsing nvidia-smi
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
flutter_background_service: ^5.0.5 flutter_background_service: ^5.0.5
@@ -31,6 +30,7 @@ dependencies:
flutter_adaptive_scaffold: ^0.1.10+2 flutter_adaptive_scaffold: ^0.1.10+2
device_info_plus: ^10.1.0 device_info_plus: ^10.1.0
extended_image: ^8.2.1 extended_image: ^8.2.1
locale_names: ^1.1.1
dartssh2: dartssh2:
git: git:
url: https://github.com/lollipopkit/dartssh2 url: https://github.com/lollipopkit/dartssh2
@@ -55,18 +55,24 @@ dependencies:
git: git:
url: https://github.com/lollipopkit/plain_notification_token url: https://github.com/lollipopkit/plain_notification_token
ref: v1.0.23 ref: v1.0.23
webdav_client:
git:
url: https://github.com/lollipopkit/webdav_client
ref: v1.0.66
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.50 ref: v1.0.89
dependency_overrides: dependency_overrides:
# dartssh2: # dartssh2:
# path: ../dartssh2 # path: ../dartssh2
# fl_lib:
# path: ../fl_lib
# xterm: # xterm:
# path: ../xterm.dart # path: ../xterm.dart
# fl_lib:
# path: ../fl_lib
# fl_build:
# path: ../fl_build
dev_dependencies: dev_dependencies:
flutter_native_splash: ^2.1.6 flutter_native_splash: ^2.1.6
@@ -76,10 +82,9 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
fl_build: fl_build:
# path: ../fl_build
git: git:
url: https://github.com/lppcg/fl_build.git url: https://github.com/lppcg/fl_build.git
ref: v1.0.30 ref: v1.0.35
flutter: flutter:
generate: true generate: true

View File

@@ -117,7 +117,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
double scale_factor = dpi / 96.0; double scale_factor = dpi / 96.0;
HWND window = CreateWindow( HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this); nullptr, nullptr, GetModuleHandle(nullptr), this);