Compare commits

...

74 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
6b9b8f0dbb chore: bump version 2024-06-25 13:15:29 +08:00
lollipopkit🏳️‍⚧️
5eb48b2717 fix: linux build (#422)
Fixes #421
2024-06-25 11:48:56 +08:00
lollipopkit🏳️‍⚧️
5339cfca70 chore: lib vers bump (#420)
Fixes #419
2024-06-25 11:16:34 +08:00
lollipopkit🏳️‍⚧️
1462b2d0b8 fix: always enter intro page (#418)
Fixes #417
2024-06-24 16:32:37 +08:00
lollipopkit🏳️‍⚧️
3798a23183 chore: bump version 2024-06-24 00:56:45 +08:00
lollipopkit🏳️‍⚧️
ddaf916170 feat: intro page (#416)
Fixes #415
2024-06-24 00:43:52 +08:00
lollipopkit🏳️‍⚧️
d6e37b058f fix: docker parse (#414)
Fixes #395
2024-06-23 18:37:05 +08:00
lollipopkit🏳️‍⚧️
2e9ad7d7cb chore: missing l10n noLineChart (#413)
Fixes #412
2024-06-23 15:39:30 +08:00
lollipopkit🏳️‍⚧️
190da74f66 chore: add participants (#410)
Fixes #409
2024-06-23 00:27:26 +08:00
lollipopkit🏳️‍⚧️
f1315dda7f fix: batch delete servers (#408)
Fixes #393
2024-06-23 00:24:30 +08:00
lollipopkit🏳️‍⚧️
43e6105eb3 fix: hideTitleBar doesn't work (#407)
Fixes #406
2024-06-22 22:58:22 +08:00
lollipopkit🏳️‍⚧️
d785209eb6 opt.: share on desktop (#405) 2024-06-22 22:50:17 +08:00
lollipopkit🏳️‍⚧️
da8b6a9010 feat: remember window size (#404)
Fixes #398
2024-06-22 21:52:48 +08:00
lollipopkit🏳️‍⚧️
1fd68722da fix: watchos settings (#403)
Fixes #401
2024-06-22 21:09:05 +08:00
lollipopkit🏳️‍⚧️
c9a2c1d0e4 Android - default to English (#402) 2024-06-18 14:39:18 +08:00
lollipopkit
161f536a62 fix: conform fdroid version fmt
Signed-off-by: lollipopkit <10864310+lollipopkit@users.noreply.github.com>
2024-06-13 23:51:10 +08:00
lollipopkit
1a32e9944e opt.: put version logic somewhere else (#381)
Signed-off-by: lollipopkit <10864310+lollipopkit@users.noreply.github.com>
2024-06-13 22:40:18 +08:00
lollipopkit
6deb753198 fix: version change logic (#381) 2024-06-13 21:29:03 +08:00
lollipopkit
4e33a98631 chore: fl_build 2024-06-13 21:22:44 +08:00
lollipopkit
dcb9464d8f fix
Signed-off-by: lollipopkit <10864310+lollipopkit@users.noreply.github.com>
2024-06-13 20:39:06 +08:00
lollipopkit
b94936b29f fix: reproducible (#381)
Signed-off-by: lollipopkit <10864310+lollipopkit@users.noreply.github.com>
2024-06-13 20:28:22 +08:00
lollipopkit
108d0a5a5b chore: bump version
Signed-off-by: lollipopkit <10864310+lollipopkit@users.noreply.github.com>
2024-06-13 00:45:24 +08:00
Integral
4814a2de28 Merge pull request #391 from Integral-Tech/add-abiCodes
Add abiCodes
2024-06-12 16:21:13 +00:00
Integral
5d8eeff502 Add abiCodes 2024-06-13 00:18:04 +08:00
lollipopkit
0bc4087266 chore: correct version for fdroid 2024-06-13 00:00:20 +08:00
lollipopkit
921209b901 chore: specify flutter version (#381) 2024-06-12 19:51:04 +08:00
lollipopkit
fa9d754470 rm: android wear settings 2024-06-12 19:42:38 +08:00
Integral
1f50a1f0f4 Merge pull request #389 from Integral-Tech/clean-up-proguard-rules
fix: clean up proguard-rules to remove depends on google libraries
2024-06-11 18:03:44 +00:00
Integral
80e84c0421 fix: clean up proguard-rules to remove depends on google libraries 2024-06-12 01:36:29 +08:00
lollipopkit
5059872c3f chore: README 2024-06-11 22:54:48 +08:00
lollipopkit
8add244776 fix: fdroid version fmt 2024-06-11 22:47:48 +08:00
lollipopkit
04e23fd7e4 fix: pubspec version 2024-06-11 22:18:27 +08:00
lollipopkit
336b11b808 chore: change dart pkg id 2024-06-11 22:06:29 +08:00
lollipopkit
8d9dba361c chore: completely rm countly 2024-06-11 22:03:17 +08:00
Integral
64ce3638cb chore: add distributionSha256Sum in gradle (#385) 2024-06-11 18:43:41 +08:00
lollipopkit
f3ceb73f0e fix: rm gms (#379 #381) 2024-06-11 16:40:05 +08:00
Integral
131dbe934c chore: setup fastlane for F-Droid submission (#384) 2024-06-11 16:18:39 +08:00
lollipopkit
58288e89bd Merge branch 'main' of github.com:lollipopkit/flutter_server_box 2024-06-10 21:35:59 +08:00
lollipopkit🏳️‍⚧️
22c43c7124 Lollipopkit/issue382 (#383) 2024-06-10 21:34:56 +08:00
lollipopkit
2bf0b25ee5 fix: podman term 2024-06-10 21:32:45 +08:00
lollipopkit
3bc03c1364 opt.: add tip (#380 #382) 2024-06-10 21:29:41 +08:00
lollipopkit
490d71f8c9 feat: snippet with term ctrl (#380 #382) 2024-06-10 21:08:33 +08:00
lollipopkit
edceb5900e opt.: write script to server tip 2024-06-09 13:57:02 +08:00
lollipopkit
e5ef28415b opt.: ssh tab 2024-06-08 21:39:42 +08:00
lollipopkit
46b98df153 Merge branch 'dev' 2024-06-08 20:58:41 +08:00
lollipopkit
9f34021c90 fix: docker ps parse if id/name is too long 2024-06-08 20:57:56 +08:00
lollipopkit
8121eef839 opt.: RNode 2024-06-08 15:35:19 +08:00
lollipopkit
da48d1f66c opt.: IME popup after opening drawer if ssh term is focusing 2024-06-08 13:53:23 +08:00
lollipopkit
b167287c5b opt.: ssh tab page's tab bar 2024-06-07 23:53:13 +08:00
lollipopkit
41f9da6bf8 fix: ssh tab PageView animteToPage 2024-06-07 21:51:00 +08:00
lollipopkit
e7c7fc8186 fix: ssh tab name generaton 2024-06-07 21:43:38 +08:00
lollipopkit
b950dd2d68 fix: ssh tab 2024-06-07 21:09:17 +08:00
lollipopkit
6d34de14d3 Merge branch 'dev' 2024-06-06 21:26:03 +08:00
lollipopkit
a5a84c0cdd fix: podman log 2024-06-06 18:52:20 +08:00
lollipopkit
701b1b811f feat: beta program 2024-06-06 16:18:10 +08:00
lollipopkit
97267cdfbf fix: docker container status (#374) 2024-06-05 21:58:19 +08:00
lollipopkit
40ce37d230 opt.: sftp del dir 2024-06-05 18:48:44 +08:00
lollipopkit
8a9ade355c fix: update check 2024-06-05 18:26:02 +08:00
lollipopkit
9bffec64b5 fix: wol cfg (#373) 2024-06-05 18:16:17 +08:00
lollipopkit
a03ee2ae0e fix: term help 2024-06-05 11:12:43 +08:00
PaperCube
ee889235fe Fixed UI representation of server reorder page (#372) 2024-06-04 18:57:57 +01:00
lollipopkit
94d6d80497 chore: README 2024-06-04 23:17:32 +08:00
lollipopkit
413c45a559 opt.: backup 2024-06-04 22:33:59 +08:00
lollipopkit
6dc5536c48 fix: batch import 2024-06-04 19:57:56 +08:00
lollipopkit
76c4bf56fa fix: sync 2024-06-02 16:04:45 +08:00
lollipopkit
a0c6642230 fix: ios watch config 2024-06-02 15:50:01 +08:00
lollipopkit
4198d7bd13 opt.: ios watch config 2024-06-02 15:30:21 +08:00
lollipopkit
b06fddec07 new: windows release actions 2024-06-02 15:29:53 +08:00
lollipopkit
d1f14bee59 opt.: speed up docker page 2024-06-01 22:36:02 +08:00
lollipopkit
8953f63197 opt.: pve 2024-06-01 17:10:22 +08:00
lollipopkit🏳️‍⚧️
193d80d826 Merge pull request #370 from leganck/main
Add pve http proxy
2024-05-31 14:15:18 +08:00
leganck
9e308792aa Add pve http proxy 2024-05-30 23:30:45 +08:00
lollipopkit
fbabd8c351 new: wear settings (#358) & opt.: android widget edit 2024-05-27 20:52:46 +08:00
lollipopkit
1a3cb09ca2 fix: ssh term (#365) 2024-05-27 11:32:52 +08:00
146 changed files with 1990 additions and 1344 deletions

View File

@@ -11,42 +11,62 @@ permissions:
jobs: jobs:
releaseAL: releaseAL:
name: Release android and linux name: Release android and linux
runs-on: ubuntu-latest runs-on: ubuntu-22.04
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:
channel: 'stable'
flutter-version: '3.22.2'
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Fetch secrets
run: |
curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key
curl -u ${{ secrets.BASIC_AUTH }} -o android/key.properties ${{ secrets.URL_PREFIX }}key.properties
- name: Build - name: Build
run: dart run fl_build -p android,linux run: dart run fl_build -p android,linux
- name: Rename for fdroid
run: |
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_arm64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm64.apk
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_arm.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm.apk
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_amd64.apk
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
files: | files: |
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm64.apk
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm.apk
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_amd64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_amd64.apk
${{ env.APP_NAME }}_amd64.AppImage ${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.AppImage
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# releaseWin: releaseWin:
# name: Release windows name: Release windows
# runs-on: windows-latest runs-on: windows-latest
# steps: steps:
# - name: Checkout - name: Checkout
# uses: actions/checkout@v4 uses: actions/checkout@v4
# - name: Install Flutter with:
# uses: subosito/flutter-action@v2 fetch-depth: '0'
# - name: Build - name: Install Flutter
# run: dart run fl_build -p windows uses: subosito/flutter-action@v2
# - name: Create Release - name: Build
# uses: softprops/action-gh-release@v1 run: dart run fl_build -p windows
# with: - name: Create Release
# files: | uses: softprops/action-gh-release@v2
# ${{ env.APP_NAME }}_amd64_windows.zip with:
# env: files: |
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_windows_amd64.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# releaseApple: # releaseApple:
# name: Release ios and macos # name: Release ios and macos
@@ -56,10 +76,13 @@ jobs:
# uses: actions/checkout@v4 # uses: actions/checkout@v4
# - name: Install Flutter # - name: Install Flutter
# uses: subosito/flutter-action@v2 # uses: subosito/flutter-action@v2
# with:
# channel: 'stable'
# flutter-version: '3.22.2'
# - name: Build # - name: Build
# run: dart run fl_build -p ios,mac # run: dart run fl_build -p ios,mac
# - name: Create Release # - name: Create Release
# uses: softprops/action-gh-release@v1 # uses: softprops/action-gh-release@v2
# with: # with:
# files: | # files: |
# ${{ env.APP_NAME }}_universal_macos.zip # ${{ env.APP_NAME }}_universal_macos.zip

3
.gitignore vendored
View File

@@ -57,9 +57,10 @@ test.dart
# Linux release # Linux release
linux.AppDir linux.AppDir
ServerBox-x86_64.AppImage **/*.AppImage
untranlated.json untranlated.json
.vscode/settings.json .vscode/settings.json
more_build_data.json more_build_data.json
trans.txt

View File

@@ -4,7 +4,6 @@ English | [简体中文](README_zh.md)
<p align="center"> <p align="center">
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink"> <img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
<img alt="countly" src="https://img.shields.io/badge/analysis-countly-pink">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink"> <img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
</p> </p>
@@ -14,45 +13,35 @@ A Flutter project which provide charts to display <a href="../../issues/43">Linu
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>. Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
</p> </p>
## ⬇️ Download ## ⬇️ Download
[iOS](https://apps.apple.com/app/id1586449703) / [Android](https://cdn.lolli.tech/serverbox/latest.apk) / [macOS](https://apps.apple.com/app/id1586449703): Full support with my own certificate 🎉 **The `Android / Linux / Windows` version are now built via GitHub Actions**
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): Basically tested, with debug certificate
[iOS & macOS](https://apps.apple.com/app/id1586449703) / [Android & Linux & Windows](https://github.com/lollipopkit/flutter_server_box/releases)
- All deprecated versions before `v930` can be found in [here](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid).
- To prevent injection attacks and etc., please don't download from untrusted sources. eg: Gitee release is not related to this project.
## 🔖 Feature ## 🔖 Feature
- Status chart, `SSH` Terminal, `SFTP`, `Docker & Pkg & Process`, Code editor... - `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Pkg & Process`...
- Platform specific: `Bio auth``Msg push``Home widget``watchOS App`... - Platform specific: `Bio auth``Msg push``Home widget``watchOS App`...
- Localization - 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, 简体中文
- Español, Русский язык, Português, 日本語 (Generated by GPT)
- Deutsch (@its-tom) / 繁體中文 (@kalashnikov) / Indonesian (@azkadev) / Français (@FrancXPT) / Dutch (@QazCetelic)
## 🏙️ ScreenShots ## 🏙️ ScreenShots
<table> <table>
<tr> <tr>
<td> <td><img width="277px" src="imgs/server.png"></td>
<img width="277px" src="imgs/server.png"> <td><img width="277px" src="imgs/detail.png"></td>
</td> <td><img width="277px" src="imgs/sftp.png"></td>
<td>
<img width="277px" src="imgs/detail.png">
</td>
<td>
<img width="277px" src="imgs/sftp.png">
</td>
</tr> </tr>
</table> </table>
<table> <table>
<tr> <tr>
<td> <td><img width="277px" src="imgs/editor.png"> </td>
<img width="277px" src="imgs/editor.png"> <td><img width="277px" src="imgs/ssh.png"></td>
</td> <td><img width="277px" src="imgs/docker.png"></td>
<td>
<img width="277px" src="imgs/ssh.png">
</td>
<td>
<img width="277px" src="imgs/docker.png">
</td>
</tr> </tr>
</table> </table>
@@ -66,9 +55,7 @@ Before you open an issue, please read the following:
2. Make sure whether the issue is caused by ServerBox app. 2. Make sure whether the issue is caused by ServerBox app.
3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted. 3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted.
After you read the above, you can: After you read the above, you can open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
- If you have **any question or feature request**, please open a [discussion](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose).
- If ServerBox app has **any bug**, please open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
## 🧱 Contribution ## 🧱 Contribution
@@ -76,15 +63,8 @@ After you read the above, you can:
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog. - [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.
## 👏🏼 Contributors
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
</a>
## 💡 My other apps ## 💡 My other apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms. - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms.
- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - Open source 2FA app for Android, iOS and the web.
- [More](https://github.com/lollipopkit) - Tools & etc. - [More](https://github.com/lollipopkit) - Tools & etc.

View File

@@ -4,7 +4,6 @@
<p align="center"> <p align="center">
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink"> <img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
<img alt="countly" src="https://img.shields.io/badge/analysis-countly-pink">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink"> <img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
</p> </p>
@@ -16,12 +15,16 @@
## ⬇️ Download ## ⬇️ Download
[iOS](https://apps.apple.com/app/id1586449703) / [Android](https://cdn.lolli.tech/serverbox/latest.apk) / [macOS](https://apps.apple.com/app/id1586449703): 经过测试,使用自签名证书 🎉 **现在 `Android / Linux / Windows` 版本使用 GitHub Actions 构建**
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): 经过不完全测试,使用调试证书
[iOS & macOS](https://apps.apple.com/app/id1586449703) / [Android & Linux & Windows](https://github.com/lollipopkit/flutter_server_box/releases)
- 所有 `v930` 之前的版本可以在 [这里](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) 找到。
- 为了防止注入攻击等请不要从不受信任的来源下载。例如Gitee 的发行包与该项目无关。
## 🔖 特点 ## 🔖 特点
- 状态图表, `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器, 代码编辑器... - `状态图表`CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器...
- 特殊支持:`生物认证``推送``桌面小部件``watchOS App``跟随系统颜色`... - 特殊支持:`生物认证``推送``桌面小部件``watchOS App``跟随系统颜色`...
- 本地化 - 本地化
- English, 简体中文 - English, 简体中文
@@ -32,28 +35,16 @@
## 🏙️ 截屏 ## 🏙️ 截屏
<table> <table>
<tr> <tr>
<td> <td><img width="277px" src="imgs/server.png"></td>
<img width="277px" src="imgs/server.png"> <td><img width="277px" src="imgs/detail.png"></td>
</td> <td><img width="277px" src="imgs/sftp.png"></td>
<td>
<img width="277px" src="imgs/detail.png">
</td>
<td>
<img width="277px" src="imgs/sftp.png">
</td>
</tr> </tr>
</table> </table>
<table> <table>
<tr> <tr>
<td> <td><img width="277px" src="imgs/editor.png"> </td>
<img width="277px" src="imgs/editor.png"> <td><img width="277px" src="imgs/ssh.png"></td>
</td> <td><img width="277px" src="imgs/docker.png"></td>
<td>
<img width="277px" src="imgs/ssh.png">
</td>
<td>
<img width="277px" src="imgs/docker.png">
</td>
</tr> </tr>
</table> </table>
@@ -69,9 +60,7 @@
2. 反馈问题前请检查是否是 serverbox 的问题。 2. 反馈问题前请检查是否是 serverbox 的问题。
3. 欢迎所有有效、正面的反馈主观比如你觉得其他UI更好看的反馈不一定会接受 3. 欢迎所有有效、正面的反馈主观比如你觉得其他UI更好看的反馈不一定会接受
确认了解上述内容后 确认了解上述内容后,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
- 如果你有**任何问题或者功能请求**,请在 [讨论](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose) 中交流。
- 如果 ServerBox app 有**任何 bug**,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
## 🧱 贡献 ## 🧱 贡献
@@ -79,15 +68,8 @@
- [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。 - [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
## 👏🏼 贡献者
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
</a>
## 💡 我的其它 Apps ## 💡 我的其它 Apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。 - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。
- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - 开源的 2FA 应用。
- [更多](https://github.com/lollipopkit) - 工具 & etc. - [更多](https://github.com/lollipopkit) - 工具 & etc.

View File

@@ -100,3 +100,14 @@ flutter {
} }
dependencies {} dependencies {}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
}

View File

@@ -1,7 +1 @@
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class com.jcraft.** { *; } -keep class com.jcraft.** { *; }

View File

@@ -3,3 +3,4 @@ distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80

View File

@@ -0,0 +1,6 @@
A Flutter project which provide charts to display Linux server status and tools to manage server.
Especially thanks to dartssh2 & xterm.dart.
* Status chart (CPU, Sensors, GPU...), SSH Term, SFTP, Docker & Pkg & Process...
* Platform specific: Bio auth、Msg push、Home widget、watchOS App...
* English, 简体中文; Deutsch, 繁體中文, Indonesian, Français, Dutch; Español, Русский язык, Português, 日本語

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -0,0 +1 @@
A server status & toolbox app using Flutter

View File

@@ -0,0 +1 @@
ServerBox

View File

@@ -0,0 +1,7 @@
使用 Flutter 开发的 Linux 服务器工具箱,提供服务器状态图表和管理工具。
特别感谢 dartssh2 & xterm.dart。
特点:
* 状态图表CPU、传感器、GPU 等), SSH 终端, SFTP, Docker & 包 & 进程管理器...
* 特殊支持生物认证、推送、桌面小部件、watchOS App、跟随系统颜色...
* 本地化 (English, 简体中文, Español, Русский язык, Português, 日本語, Deutsch, 繁體中文, Indonesian, Français, Dutch

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -0,0 +1 @@
使用 Flutter 开发的服务器状态和工具箱应用

View File

@@ -0,0 +1 @@
ServerBox

View File

@@ -1,5 +1,5 @@
PODS: PODS:
- countly_flutter (24.4.0): - device_info_plus (0.0.1):
- Flutter - Flutter
- file_picker (0.0.1): - file_picker (0.0.1):
- Flutter - Flutter
@@ -10,7 +10,7 @@ PODS:
- Flutter - Flutter
- icloud_storage (0.0.1): - icloud_storage (0.0.1):
- Flutter - Flutter
- local_auth_ios (0.0.1): - local_auth_darwin (0.0.1):
- Flutter - Flutter
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
@@ -21,8 +21,6 @@ PODS:
- Flutter - Flutter
- plain_notification_token (0.0.1): - plain_notification_token (0.0.1):
- Flutter - Flutter
- r_upgrade (0.0.1):
- Flutter
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@@ -36,18 +34,17 @@ PODS:
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`) - flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`) - icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`) - plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`)
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -55,8 +52,8 @@ DEPENDENCIES:
- watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`) - watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
countly_flutter: device_info_plus:
:path: ".symlinks/plugins/countly_flutter/ios" :path: ".symlinks/plugins/device_info_plus/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
@@ -67,8 +64,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
icloud_storage: icloud_storage:
:path: ".symlinks/plugins/icloud_storage/ios" :path: ".symlinks/plugins/icloud_storage/ios"
local_auth_ios: local_auth_darwin:
:path: ".symlinks/plugins/local_auth_ios/ios" :path: ".symlinks/plugins/local_auth_darwin/darwin"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation: path_provider_foundation:
@@ -77,8 +74,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
plain_notification_token: plain_notification_token:
:path: ".symlinks/plugins/plain_notification_token/ios" :path: ".symlinks/plugins/plain_notification_token/ios"
r_upgrade:
:path: ".symlinks/plugins/r_upgrade/ios"
share_plus: share_plus:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation: shared_preferences_foundation:
@@ -91,21 +86,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/watch_connectivity/ios" :path: ".symlinks/plugins/watch_connectivity/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
countly_flutter: 5d2febe00242796cf569662e5d47da241f31b115 device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287 file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 local_auth_darwin: 4d56c90c2683319835a61274b57620df9c4520ab
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1 plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82 watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82

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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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 = 918; CURRENT_PROJECT_VERSION = 992;
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.918; MARKETING_VERSION = 1.0.992;
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

@@ -1,12 +1,16 @@
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:fl_lib/l10n/gen/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:toolbox/data/res/build_data.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/res/rebuild.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/rebuild.dart';
import 'package:toolbox/view/page/home/home.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/home/home.dart';
import 'package:icons_plus/icons_plus.dart';
part 'intro.dart';
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
@@ -15,8 +19,8 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
_setup(context); _setup(context);
return ListenableBuilder( return ListenableBuilder(
listenable: RebuildNodes.app, listenable: RNodes.app,
builder: (_, __) { 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);
@@ -71,35 +75,44 @@ class MyApp extends StatelessWidget {
...AppLocalizations.localizationsDelegates, ...AppLocalizations.localizationsDelegates,
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
localeListResolutionCallback: LocaleUtil.resolve,
title: BuildData.name, title: BuildData.name,
themeMode: themeMode, themeMode: themeMode,
theme: light, theme: light,
darkTheme: tMode < 3 ? dark : _getAmoledTheme(dark), darkTheme: tMode < 3 ? dark : dark.toAmoled,
home: _buildAppContent(ctx), home: _buildAppContent(ctx),
); );
} }
Widget _buildAppContent(BuildContext ctx) { Widget _buildAppContent(BuildContext ctx) {
//if (Pros.app.isWearOS) return const WearHome(); //if (Pros.app.isWearOS) return const WearHome();
return const HomePage(); 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 {
SystemUIs.setTransparentNavigationBar(context); SystemUIs.setTransparentNavigationBar(context);
} }
ThemeData _getAmoledTheme(ThemeData darkTheme) => darkTheme.copyWith(
scaffoldBackgroundColor: Colors.black,
dialogBackgroundColor: Colors.black,
drawerTheme: const DrawerThemeData(backgroundColor: Colors.black),
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
bottomSheetTheme:
const BottomSheetThemeData(backgroundColor: Colors.black),
listTileTheme: const ListTileThemeData(tileColor: Colors.transparent),
cardTheme: const CardTheme(color: Colors.black12),
navigationBarTheme:
const NavigationBarThemeData(backgroundColor: Colors.black),
popupMenuTheme: const PopupMenuThemeData(color: Colors.black),
);

View File

@@ -1,5 +1,5 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:toolbox/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
abstract final class BgRunMC { abstract final class BgRunMC {
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain'); static const _channel = MethodChannel('${Miscs.pkgName}/app_retain');

View File

@@ -1,6 +1,6 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:toolbox/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
abstract final class HomeWidgetMC { abstract final class HomeWidgetMC {
static const _channel = MethodChannel('${Miscs.pkgName}/home_widget'); static const _channel = MethodChannel('${Miscs.pkgName}/home_widget');

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
extension BuildDataX on BuildData { extension BuildDataX on BuildData {
static const versionStr = 'v1.0.${BuildData.build}'; static const versionStr = 'v1.0.${BuildData.build}';

View File

@@ -79,7 +79,9 @@ extension SSHClientX on SSHClient {
isRequestingPwd = true; isRequestingPwd = true;
final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1); final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1);
if (context == null) return; if (context == null) return;
final pwd = await context.showPwdDialog(title: user, id: id); final pwd = context.mounted
? await context.showPwdDialog(title: user, id: id)
: null;
if (pwd == null || pwd.isEmpty) { if (pwd == null || pwd.isEmpty) {
session.kill(SSHSignal.TERM); session.kill(SSHSignal.TERM);
} else { } else {

View File

@@ -1,27 +1,27 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:toolbox/view/page/backup.dart'; import 'package:server_box/view/page/backup.dart';
import 'package:toolbox/view/page/container.dart'; import 'package:server_box/view/page/container.dart';
import 'package:toolbox/view/page/home/home.dart'; import 'package:server_box/view/page/home/home.dart';
import 'package:toolbox/view/page/iperf.dart'; import 'package:server_box/view/page/iperf.dart';
import 'package:toolbox/view/page/ping.dart'; import 'package:server_box/view/page/ping.dart';
import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:server_box/view/page/private_key/edit.dart';
import 'package:toolbox/view/page/private_key/list.dart'; import 'package:server_box/view/page/private_key/list.dart';
import 'package:toolbox/view/page/pve.dart'; import 'package:server_box/view/page/pve.dart';
import 'package:toolbox/view/page/server/detail/view.dart'; import 'package:server_box/view/page/server/detail/view.dart';
import 'package:toolbox/view/page/setting/platform/android.dart'; import 'package:server_box/view/page/setting/platform/android.dart';
import 'package:toolbox/view/page/setting/platform/ios.dart'; import 'package:server_box/view/page/setting/platform/ios.dart';
import 'package:toolbox/view/page/setting/seq/srv_func_seq.dart'; import 'package:server_box/view/page/setting/seq/srv_func_seq.dart';
import 'package:toolbox/view/page/snippet/result.dart'; import 'package:server_box/view/page/snippet/result.dart';
import 'package:toolbox/view/page/ssh/page.dart'; import 'package:server_box/view/page/ssh/page.dart';
import 'package:toolbox/view/page/setting/seq/virt_key.dart'; import 'package:server_box/view/page/setting/seq/virt_key.dart';
import 'package:toolbox/view/page/storage/local.dart'; import 'package:server_box/view/page/storage/local.dart';
import '../data/model/server/snippet.dart'; import '../data/model/server/snippet.dart';
import '../view/page/editor.dart'; import '../view/page/editor.dart';
@@ -102,12 +102,14 @@ class AppRoutes {
Key? key, Key? key,
required ServerPrivateInfo spi, required ServerPrivateInfo spi,
String? initCmd, String? initCmd,
Snippet? initSnippet,
}) { }) {
return AppRoutes( return AppRoutes(
SSHPage( SSHPage(
key: key, key: key,
spi: spi, spi: spi,
initCmd: initCmd, initCmd: initCmd,
initSnippet: initSnippet,
), ),
'ssh_term', 'ssh_term',
); );
@@ -246,4 +248,8 @@ class AppRoutes {
static AppRoutes pve({Key? key, required ServerPrivateInfo spi}) { static AppRoutes pve({Key? key, required ServerPrivateInfo spi}) {
return AppRoutes(PvePage(key: key, spi: spi), 'pve'); return AppRoutes(PvePage(key: key, spi: spi), 'pve');
} }
static AppRoutes kvEditor({Key? key, required Map<String, String> data}) {
return AppRoutes(KvEditor(key: key, data: data), 'kv_editor');
}
} }

View File

@@ -2,14 +2,9 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:plain_notification_token/plain_notification_token.dart'; import 'package:plain_notification_token/plain_notification_token.dart';
Future<String?> getToken() async { Future<String?> getToken() async {
if (isIOS) { if (!isIOS) return null;
final plainNotificationToken = PlainNotificationToken(); final instance = ApnsToken()..requestPermission();
plainNotificationToken.requestPermission(); // Wait until Permission dialog closed
await instance.onIosSettingsRegistered.first;
// If you want to wait until Permission dialog close, return await instance.getToken();
// you need wait changing setting registered.
await plainNotificationToken.onIosSettingsRegistered.first;
return await plainNotificationToken.getToken();
}
return null;
} }

View File

@@ -2,8 +2,8 @@ import 'dart:async';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import '../../data/model/server/server_private_info.dart'; import '../../data/model/server/server_private_info.dart';

View File

@@ -2,9 +2,9 @@ import 'dart:async';
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:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
abstract final class KeybordInteractive { abstract final class KeybordInteractive {
static FutureOr<List<String>?> defaultHandle( static FutureOr<List<String>?> defaultHandle(

View File

@@ -5,8 +5,9 @@ import 'package:computer/computer.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:icloud_storage/icloud_storage.dart'; import 'package:icloud_storage/icloud_storage.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:toolbox/data/model/app/backup.dart'; import 'package:server_box/data/model/app/backup.dart';
import 'package:toolbox/data/model/app/sync.dart'; import 'package:server_box/data/model/app/sync.dart';
import 'package:server_box/data/res/misc.dart';
import '../../../data/model/app/error.dart'; import '../../../data/model/app/error.dart';
@@ -198,14 +199,13 @@ abstract final class ICloud {
} }
static Future<void> sync() async { static Future<void> sync() async {
final result = await download(relativePath: Paths.bakName); final result = await download(relativePath: Miscs.bakFileName);
if (result != null) { if (result != null) {
_logger.warning('Download backup failed: $result');
await backup(); await backup();
return; return;
} }
final dlFile = await File(Paths.bakPath).readAsString(); final dlFile = await File(Paths.bak).readAsString();
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile); final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
await dlBak.restore(); await dlBak.restore();
@@ -214,7 +214,7 @@ abstract final class ICloud {
static Future<void> backup() async { static Future<void> backup() async {
await Backup.backup(); await Backup.backup();
final uploadResult = await upload(relativePath: Paths.bakName); final uploadResult = await upload(relativePath: Miscs.bakFileName);
if (uploadResult != null) { if (uploadResult != null) {
_logger.warning('Upload backup failed: $uploadResult'); _logger.warning('Upload backup failed: $uploadResult');
} else { } else {

View File

@@ -3,9 +3,10 @@ import 'dart:io';
import 'package:computer/computer.dart'; import 'package:computer/computer.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:toolbox/data/model/app/backup.dart'; import 'package:server_box/data/model/app/backup.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart';
import 'package:webdav_client/webdav_client.dart'; import 'package:webdav_client/webdav_client.dart';
abstract final class Webdav { abstract final class Webdav {
@@ -96,15 +97,14 @@ abstract final class Webdav {
} }
static Future<void> sync() async { static Future<void> sync() async {
final result = await download(relativePath: Paths.bakName); final result = await download(relativePath: Miscs.bakFileName);
if (result != null) { if (result != null) {
_logger.warning('Download failed: $result');
await backup(); await backup();
return; return;
} }
try { try {
final dlFile = await File(Paths.bakPath).readAsString(); final dlFile = await File(Paths.bak).readAsString();
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile); final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
await dlBak.restore(); await dlBak.restore();
} catch (e) { } catch (e) {
@@ -117,7 +117,7 @@ abstract final class Webdav {
/// Create a local backup and upload it to WebDAV /// Create a local backup and upload it to WebDAV
static Future<void> backup() async { static Future<void> backup() async {
await Backup.backup(); await Backup.backup();
final uploadResult = await upload(relativePath: Paths.bakName); final uploadResult = await upload(relativePath: Miscs.bakFileName);
if (uploadResult != null) { if (uploadResult != null) {
_logger.warning('Upload failed: $uploadResult'); _logger.warning('Upload failed: $uploadResult');
} else { } else {

View File

@@ -3,12 +3,13 @@ import 'dart:io';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:toolbox/data/res/rebuild.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/rebuild.dart';
import 'package:server_box/data/res/store.dart';
const backupFormatVersion = 1; const backupFormatVersion = 1;
@@ -74,7 +75,7 @@ class Backup {
static Future<String> backup([String? name]) async { static Future<String> backup([String? name]) async {
final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson())); final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson()));
final path = '${Paths.doc}/${name ?? 'srvbox_bak.json'}'; final path = '${Paths.doc}/${name ?? Miscs.bakFileName}';
await File(path).writeAsString(result); await File(path).writeAsString(result);
return path; return path;
} }
@@ -169,7 +170,7 @@ class Backup {
} }
Pros.reload(); Pros.reload();
RebuildNodes.app.rebuild(); RNodes.app.build();
_logger.info('Restore success'); _logger.info('Restore success');
} }

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
enum ErrFrom { enum ErrFrom {
unknown, unknown,

View File

@@ -1,5 +0,0 @@
typedef GhId = String;
extension GhIdX on GhId {
String get url => 'https://github.com/$this';
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
enum ContainerMenu { enum ContainerMenu {
start, start,

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
part 'server_func.g.dart'; part 'server_func.g.dart';

View File

@@ -1,7 +1,7 @@
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
part 'net_view.g.dart'; part 'net_view.g.dart';

View File

@@ -1,23 +0,0 @@
import 'package:flutter/foundation.dart';
class RebuildNode implements Listenable {
final List<VoidCallback> _listeners = [];
RebuildNode();
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
void rebuild() {
for (var listener in _listeners) {
listener();
}
}
}

View File

@@ -1,8 +1,8 @@
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:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
enum ServerDetailCards { enum ServerDetailCards {
about(Icons.info), about(Icons.info),

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import '../../res/build_data.dart'; import '../../res/build_data.dart';
import '../server/system.dart'; import '../server/system.dart';

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/view/page/ping.dart'; import 'package:server_box/view/page/ping.dart';
import 'package:toolbox/view/page/server/tab.dart'; import 'package:server_box/view/page/server/tab.dart';
import 'package:toolbox/view/page/snippet/list.dart'; import 'package:server_box/view/page/snippet/list.dart';
import 'package:toolbox/view/page/ssh/tab.dart'; import 'package:server_box/view/page/ssh/tab.dart';
enum AppTab { enum AppTab {
server, server,

View File

@@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/model/container/type.dart'; import 'package:server_box/data/model/container/type.dart';
abstract final class ContainerImg { abstract final class ContainerImg {
final String? repository = null; final String? repository = null;
@@ -72,7 +72,7 @@ final class DockerImg implements ContainerImg {
final String repository; final String repository;
final String size; final String size;
@override @override
final String tag; final String? tag;
DockerImg({ DockerImg({
required this.containers, required this.containers,
@@ -95,14 +95,30 @@ final class DockerImg implements ContainerImg {
String toRawJson() => json.encode(toJson()); String toRawJson() => json.encode(toJson());
factory DockerImg.fromJson(Map<String, dynamic> json) => DockerImg( factory DockerImg.fromJson(Map<String, dynamic> json) {
containers: json["Containers"], final containers = switch (json["Containers"]) {
createdAt: json["CreatedAt"], final String a => a,
id: json["ID"], final Object? a => a.toString(),
repository: json["Repository"], };
size: json["Size"], final repo = switch (json["Repository"] ?? json["Names"]) {
tag: json["Tag"], final String a => a,
); final List a => a.firstOrNull.toString(),
final Object? a => a.toString(),
};
final size = switch (json["Size"]) {
final String a => a,
final int a => a.bytes2Str,
final Object? a => a.toString(),
};
return DockerImg(
containers: containers,
createdAt: json["CreatedAt"],
id: json["ID"] ?? json["Id"] ?? '',
repository: repo,
size: size,
tag: json["Tag"],
);
}
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"Containers": containers, "Containers": containers,

View File

@@ -1,10 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/model/container/type.dart'; import 'package:server_box/data/model/container/type.dart';
import 'package:server_box/data/res/misc.dart';
abstract final class ContainerPs { sealed class ContainerPs {
final String? id = null; final String? id = null;
final String? image = null; final String? image = null;
String? get name; String? get name;
@@ -16,7 +17,7 @@ abstract final class ContainerPs {
String? net; String? net;
String? disk; String? disk;
factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s); factory ContainerPs.fromRaw(String s, ContainerType typ) => typ.ps(s);
void parseStats(String s); void parseStats(String s);
} }
@@ -110,8 +111,6 @@ final class PodmanPs implements ContainerPs {
} }
final class DockerPs implements ContainerPs { final class DockerPs implements ContainerPs {
final String? command;
final String? createdAt;
@override @override
final String? id; final String? id;
@override @override
@@ -129,8 +128,6 @@ final class DockerPs implements ContainerPs {
String? disk; String? disk;
DockerPs({ DockerPs({
this.command,
this.createdAt,
this.id, this.id,
this.image, this.image,
this.names, this.names,
@@ -141,10 +138,13 @@ final class DockerPs implements ContainerPs {
String? get name => names; String? get name => names;
@override @override
String? get cmd => command; String? get cmd => null;
@override @override
bool get running => state == 'running'; bool get running {
if (state?.contains('Exited') == true) return false;
return true;
}
@override @override
void parseStats(String s) { void parseStats(String s) {
@@ -155,26 +155,15 @@ final class DockerPs implements ContainerPs {
disk = stats['BlockIO']; disk = stats['BlockIO'];
} }
factory DockerPs.fromRawJson(String str) => /// CONTAINER ID NAMES IMAGE STATUS
DockerPs.fromJson(json.decode(str)); /// a049d689e7a1 aria2-pro p3terx/aria2-pro Up 3 weeks
factory DockerPs.parse(String raw) {
String toRawJson() => json.encode(toJson()); final parts = raw.split(Miscs.multiBlankreg);
return DockerPs(
factory DockerPs.fromJson(Map<String, dynamic> json) => DockerPs( id: parts[0],
command: json["Command"], state: parts[1],
createdAt: json["CreatedAt"], names: parts[2],
id: json["ID"], image: parts[3].trim(),
image: json["Image"], );
names: json["Names"], }
state: json["State"],
);
Map<String, dynamic> toJson() => {
"Command": command,
"CreatedAt": createdAt,
"ID": id,
"Image": image,
"Names": names,
"State": state,
};
} }

View File

@@ -1,5 +1,5 @@
import 'package:toolbox/data/model/container/image.dart'; import 'package:server_box/data/model/container/image.dart';
import 'package:toolbox/data/model/container/ps.dart'; import 'package:server_box/data/model/container/ps.dart';
enum ContainerType { enum ContainerType {
docker, docker,
@@ -7,7 +7,7 @@ enum ContainerType {
; ;
ContainerPs Function(String str) get ps => switch (this) { ContainerPs Function(String str) get ps => switch (this) {
ContainerType.docker => DockerPs.fromRawJson, ContainerType.docker => DockerPs.parse,
ContainerType.podman => PodmanPs.fromRawJson, ContainerType.podman => PodmanPs.fromRawJson,
}; };

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/data/model/server/dist.dart'; import 'package:server_box/data/model/server/dist.dart';
enum PkgManager { enum PkgManager {
apt, apt,

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/data/model/pkg/manager.dart'; import 'package:server_box/data/model/pkg/manager.dart';
class UpgradePkgInfo { class UpgradePkgInfo {
final PkgManager? _mgr; final PkgManager? _mgr;

View File

@@ -1,8 +1,8 @@
import 'dart:collection'; import 'dart:collection';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:toolbox/data/model/server/time_seq.dart'; import 'package:server_box/data/model/server/time_seq.dart';
import 'package:toolbox/data/res/status.dart'; import 'package:server_box/data/res/status.dart';
const _kCap = 30; const _kCap = 30;

View File

@@ -1,5 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/model/server/time_seq.dart'; import 'package:server_box/data/model/server/time_seq.dart';
import '../../res/misc.dart'; import '../../res/misc.dart';

View File

@@ -1,5 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
enum PveResType { enum PveResType {
lxc, lxc,

View File

@@ -1,5 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
final class SensorAdaptor { final class SensorAdaptor {
final String raw; final String raw;

View File

@@ -1,19 +1,19 @@
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:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/battery.dart'; import 'package:server_box/data/model/server/battery.dart';
import 'package:toolbox/data/model/server/conn.dart'; import 'package:server_box/data/model/server/conn.dart';
import 'package:toolbox/data/model/server/cpu.dart'; import 'package:server_box/data/model/server/cpu.dart';
import 'package:toolbox/data/model/server/disk.dart'; import 'package:server_box/data/model/server/disk.dart';
import 'package:toolbox/data/model/server/memory.dart'; import 'package:server_box/data/model/server/memory.dart';
import 'package:toolbox/data/model/server/net_speed.dart'; import 'package:server_box/data/model/server/net_speed.dart';
import 'package:toolbox/data/model/server/nvdia.dart'; import 'package:server_box/data/model/server/nvdia.dart';
import 'package:toolbox/data/model/server/sensors.dart'; import 'package:server_box/data/model/server/sensors.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:toolbox/data/model/server/temp.dart'; import 'package:server_box/data/model/server/temp.dart';
import '../app/tag_pickable.dart'; import '../app/tag_pickable.dart';

View File

@@ -1,8 +1,8 @@
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:toolbox/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:toolbox/data/model/server/wol_cfg.dart'; import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import '../app/error.dart'; import '../app/error.dart';
@@ -64,7 +64,7 @@ class ServerPrivateInfo {
final port = json["port"] as int? ?? 22; final port = json["port"] as int? ?? 22;
final user = json["user"] as String? ?? 'root'; final user = json["user"] as String? ?? 'root';
final name = json["name"] as String? ?? ''; final name = json["name"] as String? ?? '';
final pwd = json["authorization"] as String?; final pwd = json["pwd"] as String? ?? json["authorization"] as String?;
final keyId = json["pubKeyId"] as String?; final keyId = json["pubKeyId"] as String?;
final tags = (json["tags"] as List?)?.cast<String>(); final tags = (json["tags"] as List?)?.cast<String>();
final alterUrl = json["alterUrl"] as String?; final alterUrl = json["alterUrl"] as String?;
@@ -100,7 +100,7 @@ class ServerPrivateInfo {
data["port"] = port; data["port"] = port;
data["user"] = user; data["user"] = user;
if (pwd != null) { if (pwd != null) {
data["authorization"] = pwd; data["pwd"] = pwd;
} }
if (keyId != null) { if (keyId != null) {
data["pubKeyId"] = keyId; data["pubKeyId"] = keyId;

View File

@@ -1,9 +1,9 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/model/server/battery.dart'; import 'package:server_box/data/model/server/battery.dart';
import 'package:toolbox/data/model/server/nvdia.dart'; import 'package:server_box/data/model/server/nvdia.dart';
import 'package:toolbox/data/model/server/sensors.dart'; import 'package:server_box/data/model/server/sensors.dart';
import 'package:toolbox/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:toolbox/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import '../app/shell_func.dart'; import '../app/shell_func.dart';
import 'cpu.dart'; import 'cpu.dart';

View File

@@ -1,5 +1,9 @@
import 'dart:async';
import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:xterm/core.dart';
import '../app/tag_pickable.dart'; import '../app/tag_pickable.dart';
@@ -53,19 +57,116 @@ class Snippet implements TagPickable {
@override @override
String get tagName => name; String get tagName => name;
String fmtWith(ServerPrivateInfo spi) { static final fmtFinder = RegExp(r'\$\{[^{}]+\}');
final fmted = script.replaceAllMapped(
RegExp(r'\${.+?}'), String fmtWithSpi(ServerPrivateInfo spi) {
return script.replaceAllMapped(
fmtFinder,
(match) { (match) {
final key = match.group(0); final key = match.group(0);
final func = fmtArgs[key]; final func = fmtArgs[key];
if (func == null) { if (func != null) return func(spi);
return key!; // If not found, return the original content for further processing
} return key ?? '';
return func(spi);
}, },
); );
return fmted; }
Future<void> runInTerm(
Terminal terminal,
ServerPrivateInfo spi, {
bool autoEnter = false,
}) async {
final argsFmted = fmtWithSpi(spi);
final matches = fmtFinder.allMatches(argsFmted);
/// There is no [TerminalKey] in the script
if (matches.isEmpty) {
terminal.textInput(argsFmted);
if (autoEnter) terminal.keyInput(TerminalKey.enter);
return;
}
// Records all start and end indexes of the matches
final (starts, ends) = matches.fold((<int>[], <int>[]), (pre, e) {
pre.$1.add(e.start);
pre.$2.add(e.end);
return pre;
});
// Check all indexes, `(idx + 1).start` must >= `idx.end`
for (var i = 0; i < starts.length - 1; i++) {
final lastEnd = ends[i];
final nextStart = starts[i + 1];
if (nextStart < lastEnd) {
throw 'Invalid format: $nextStart < $lastEnd';
}
}
// Start term input
if (starts.first > 0) {
terminal.textInput(argsFmted.substring(0, starts.first));
}
// Process matched
for (var idx = 0; idx < starts.length; idx++) {
final start = starts[idx];
final end = ends[idx];
final key = argsFmted.substring(start, end).toLowerCase();
// Special funcs
final special = _find(SnippetFuncs.specialCtrl, key);
if (special != null) {
final raw = key.substring(special.key.length + 1, key.length - 1);
await special.value((term: terminal, raw: raw));
}
// Term keys
final termKey = _find(fmtTermKeys, key);
if (termKey != null) await _doTermKeys(terminal, termKey, key);
}
// End term input
if (ends.last < argsFmted.length) {
terminal.textInput(argsFmted.substring(ends.last));
}
if (autoEnter) terminal.keyInput(TerminalKey.enter);
}
Future<void> _doTermKeys(
Terminal terminal,
MapEntry<String, TerminalKey> termKey,
String key,
) async {
if (termKey.value == TerminalKey.enter) {
terminal.keyInput(TerminalKey.enter);
return;
}
final ctrlAlt = switch (termKey.value) {
TerminalKey.control => (ctrl: true, alt: false),
TerminalKey.alt => (ctrl: false, alt: true),
_ => (ctrl: false, alt: false),
};
// `${ctrl+ad}` -> `ctrla + d`
final chars = key.substring(termKey.key.length + 1, key.length - 1);
if (chars.isEmpty) return;
final ok = terminal.charInput(
chars.codeUnitAt(0),
ctrl: ctrlAlt.ctrl,
alt: ctrlAlt.alt,
);
if (!ok) {
Loggers.app.warning('Failed to input: $key');
}
terminal.textInput(chars.substring(1));
}
MapEntry<String, T>? _find<T>(Map<String, T> map, String key) {
return map.entries.firstWhereOrNull((e) => key.startsWith(e.key));
} }
static final fmtArgs = { static final fmtArgs = {
@@ -76,6 +177,12 @@ class Snippet implements TagPickable {
r'${id}': (ServerPrivateInfo spi) => spi.id, r'${id}': (ServerPrivateInfo spi) => spi.id,
r'${name}': (ServerPrivateInfo spi) => spi.name, r'${name}': (ServerPrivateInfo spi) => spi.name,
}; };
/// r'${ctrl+ad}' -> TerminalKey.control, a, d
static final fmtTermKeys = {
r'${ctrl': TerminalKey.control,
r'${alt': TerminalKey.alt,
};
} }
class SnippetResult { class SnippetResult {
@@ -89,3 +196,32 @@ class SnippetResult {
required this.time, required this.time,
}); });
} }
typedef SnippetFuncCtx = ({Terminal term, String raw});
abstract final class SnippetFuncs {
static final specialCtrl = {
// `${sleep 3}` -> sleep 3 seconds
r'${sleep': SnippetFuncs.sleep,
r'${enter': SnippetFuncs.enter,
};
static const help = {
'sleep': 'Sleep for a few seconds',
'enter': 'Enter a few times',
};
static FutureOr<void> sleep(SnippetFuncCtx ctx) async {
final seconds = int.tryParse(ctx.raw);
if (seconds == null) return;
final duration = Duration(seconds: seconds);
await Future.delayed(duration);
}
static FutureOr<void> enter(SnippetFuncCtx ctx) async {
final times = int.tryParse(ctx.raw) ?? 1;
for (var i = 0; i < times; i++) {
ctx.term.keyInput(TerminalKey.enter);
}
}
}

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
enum SystemType { enum SystemType {
linux._(linuxSign), linux._(linuxSign),

View File

@@ -1,4 +1,4 @@
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
class TryLimiter { class TryLimiter {
final Map<String, int> _triedTimes = {}; final Map<String, int> _triedTimes = {};

View File

@@ -1,5 +1,5 @@
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:toolbox/data/model/sftp/absolute_path.dart'; import 'package:server_box/data/model/sftp/absolute_path.dart';
class SftpBrowserStatus { class SftpBrowserStatus {
List<SftpName>? files; List<SftpName>? files;

View File

@@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import '../../../core/utils/server.dart'; import '../../../core/utils/server.dart';
import '../server/server_private_info.dart'; import '../server/server_private_info.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:xterm/core.dart'; import 'package:xterm/core.dart';
part 'virtual_key.g.dart'; part 'virtual_key.g.dart';

View File

@@ -4,13 +4,13 @@ import 'dart:convert';
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:toolbox/core/extension/ssh_client.dart'; import 'package:server_box/core/extension/ssh_client.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/container/image.dart'; import 'package:server_box/data/model/container/image.dart';
import 'package:toolbox/data/model/container/ps.dart'; import 'package:server_box/data/model/container/ps.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/model/container/type.dart'; import 'package:server_box/data/model/container/type.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
final _dockerNotFound = final _dockerNotFound =
RegExp(r"command not found|Unknown command|Command '\w+' not found"); RegExp(r"command not found|Unknown command|Command '\w+' not found");
@@ -26,7 +26,8 @@ class ContainerProvider extends ChangeNotifier {
ContainerErr? error; ContainerErr? error;
String? runLog; String? runLog;
ContainerType type; ContainerType type;
bool sudo = false; var sudoCompleter = Completer<bool>();
bool isBusy = false;
ContainerProvider({ ContainerProvider({
required this.client, required this.client,
@@ -41,6 +42,7 @@ class ContainerProvider extends ChangeNotifier {
this.type = type; this.type = type;
Stores.container.setType(type, hostId); Stores.container.setType(type, hostId);
error = runLog = items = images = version = null; error = runLog = items = images = version = null;
sudoCompleter = Completer<bool>();
notifyListeners(); notifyListeners();
await refresh(); await refresh();
} }
@@ -60,17 +62,27 @@ class ContainerProvider extends ChangeNotifier {
// return value; // return value;
// } // }
Future<bool> _requiresSudo() async { void _requiresSudo() async {
final psResult = await client?.run(_wrap(ContainerCmdType.ps.exec(type))); /// Podman is rootless
if (psResult == null) return true; if (type == ContainerType.podman) return sudoCompleter.complete(false);
if (psResult.string.toLowerCase().contains("permission denied")) { if (!Stores.setting.containerTrySudo.fetch()) {
return true; return sudoCompleter.complete(false);
} }
return false;
final res = await client?.run(_wrap(ContainerCmdType.images.exec(type)));
if (res?.string.toLowerCase().contains("permission denied") ?? false) {
return sudoCompleter.complete(true);
}
return sudoCompleter.complete(false);
} }
Future<void> refresh({bool isAuto = false}) async { Future<void> refresh({bool isAuto = false}) async {
sudo = await _requiresSudo() && Stores.setting.containerTrySudo.fetch(); if (isBusy) return;
isBusy = true;
if (!sudoCompleter.isCompleted) _requiresSudo();
final sudo = await sudoCompleter.future;
/// If sudo is required and auto refresh is enabled, skip the refresh. /// If sudo is required and auto refresh is enabled, skip the refresh.
/// Or this will ask for pwd again and again. /// Or this will ask for pwd again and again.
@@ -78,17 +90,22 @@ class ContainerProvider extends ChangeNotifier {
final includeStats = Stores.setting.containerParseStat.fetch(); final includeStats = Stores.setting.containerParseStat.fetch();
var raw = ''; var raw = '';
final cmd = _wrap(ContainerCmdType.execAll(
type,
sudo: sudo,
includeStats: includeStats,
));
final code = await client?.execWithPwd( final code = await client?.execWithPwd(
_wrap(ContainerCmdType.execAll( cmd,
type,
sudo: sudo,
includeStats: includeStats,
)),
context: context, context: context,
onStdout: (data, _) => raw = '$raw$data', onStdout: (data, _) => raw = '$raw$data',
id: hostId, id: hostId,
); );
isBusy = false;
if (!context.mounted) return;
/// Code 127 means command not found /// Code 127 means command not found
if (code == 127 || raw.contains(_dockerNotFound)) { if (code == 127 || raw.contains(_dockerNotFound)) {
error = ContainerErr(type: ContainerErrType.notInstalled); error = ContainerErr(type: ContainerErrType.notInstalled);
@@ -126,8 +143,12 @@ class ContainerProvider extends ChangeNotifier {
final psRaw = ContainerCmdType.ps.find(segments); final psRaw = ContainerCmdType.ps.find(segments);
try { try {
final lines = psRaw.split('\n'); final lines = psRaw.split('\n');
if (type == ContainerType.docker) {
/// Due to the fetched data is not in json format, skip table header
lines.removeWhere((element) => element.contains('CONTAINER ID'));
}
lines.removeWhere((element) => element.isEmpty); lines.removeWhere((element) => element.isEmpty);
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList(); items = lines.map((e) => ContainerPs.fromRaw(e, type)).toList();
} catch (e, trace) { } catch (e, trace) {
error = ContainerErr( error = ContainerErr(
type: ContainerErrType.parsePs, type: ContainerErrType.parsePs,
@@ -139,11 +160,18 @@ class ContainerProvider extends ChangeNotifier {
} }
// Parse images // Parse images
final imageRaw = ContainerCmdType.images.find(segments); final imageRaw = ContainerCmdType.images.find(segments).trim();
final isEntireJson = imageRaw.startsWith('[') && imageRaw.endsWith(']');
try { try {
final imgLines = imageRaw.split('\n'); if (isEntireJson) {
imgLines.removeWhere((element) => element.isEmpty); images = (json.decode(imageRaw) as List)
images = imgLines.map((e) => ContainerImg.fromRawJson(e, type)).toList(); .map((e) => ContainerImg.fromRawJson(json.encode(e), type))
.toList();
} else {
final lines = imageRaw.split('\n');
lines.removeWhere((element) => element.isEmpty);
images = lines.map((e) => ContainerImg.fromRawJson(e, type)).toList();
}
} catch (e, trace) { } catch (e, trace) {
error = ContainerErr( error = ContainerErr(
type: ContainerErrType.parseImages, type: ContainerErrType.parseImages,
@@ -203,7 +231,7 @@ class ContainerProvider extends ChangeNotifier {
runLog = ''; runLog = '';
final errs = <String>[]; final errs = <String>[];
final code = await client?.execWithPwd( final code = await client?.execWithPwd(
_wrap(sudo ? 'sudo -S $cmd' : cmd), _wrap((await sudoCompleter.future) ? 'sudo -S $cmd' : cmd),
context: context, context: context,
onStdout: (data, _) { onStdout: (data, _) {
runLog = '$runLog$data'; runLog = '$runLog$data';
@@ -254,7 +282,16 @@ enum ContainerCmdType {
final prefix = sudo ? 'sudo -S ${type.name}' : type.name; final prefix = sudo ? 'sudo -S ${type.name}' : type.name;
return switch (this) { return switch (this) {
ContainerCmdType.version => '$prefix version $_jsonFmt', ContainerCmdType.version => '$prefix version $_jsonFmt',
ContainerCmdType.ps => '$prefix ps -a $_jsonFmt', ContainerCmdType.ps => switch (type) {
/// TODO: Rollback to json format when permformance recovers.
/// Use [_jsonFmt] in Docker will cause the operation to slow down.
ContainerType.docker => '$prefix ps -a --format "table {{printf \\"'
'%-15.15s '
'%-30.30s '
'${"%-50.50s " * 2}\\"'
' .ID .Status .Names .Image}}"',
ContainerType.podman => '$prefix ps -a $_jsonFmt',
},
ContainerCmdType.stats => ContainerCmdType.stats =>
includeStats ? '$prefix stats --no-stream $_jsonFmt' : 'echo PASS', includeStats ? '$prefix stats --no-stream $_jsonFmt' : 'echo PASS',
ContainerCmdType.images => '$prefix image ls $_jsonFmt', ContainerCmdType.images => '$prefix image ls $_jsonFmt',
@@ -268,6 +305,6 @@ enum ContainerCmdType {
}) { }) {
return ContainerCmdType.values return ContainerCmdType.values
.map((e) => e.exec(type, sudo: sudo, includeStats: includeStats)) .map((e) => e.exec(type, sudo: sudo, includeStats: includeStats))
.join(' && echo ${ShellFunc.seperator} && '); .join('\necho ${ShellFunc.seperator}\n');
} }
} }

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
class PrivateKeyProvider extends ChangeNotifier { class PrivateKeyProvider extends ChangeNotifier {
List<PrivateKeyInfo> get pkis => _pkis; List<PrivateKeyInfo> get pkis => _pkis;

View File

@@ -6,24 +6,28 @@ import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.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:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/model/server/pve.dart'; import 'package:server_box/data/model/server/pve.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:dartssh2/dartssh2.dart';
typedef PveCtrlFunc = Future<bool> Function(String node, String id); typedef PveCtrlFunc = Future<bool> Function(String node, String id);
final class PveProvider extends ChangeNotifier { final class PveProvider extends ChangeNotifier {
final ServerPrivateInfo spi; final ServerPrivateInfo spi;
late final String addr; late String addr;
//late final SSHClient _client; late final SSHClient _client;
late final ServerSocket _serverSocket;
final List<SSHForwardChannel> _forwards = [];
int _localPort = 0;
PveProvider({required this.spi}) { PveProvider({required this.spi}) {
// final client = _spi.server?.client; final client = spi.server?.client;
// if (client == null) { if (client == null) {
// throw Exception('Server client is null'); throw Exception('Server client is null');
// } }
// _client = client; _client = client;
final addr = spi.custom?.pveAddr; final addr = spi.custom?.pveAddr;
if (addr == null) { if (addr == null) {
err.value = 'PVE address is null'; err.value = 'PVE address is null';
@@ -41,6 +45,7 @@ final class PveProvider extends ChangeNotifier {
..httpClientAdapter = IOHttpClientAdapter( ..httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () { createHttpClient: () {
final client = HttpClient(); final client = HttpClient();
client.connectionFactory = cf;
if (_ignoreCert) { if (_ignoreCert) {
client.badCertificateCallback = (_, __, ___) => true; client.badCertificateCallback = (_, __, ___) => true;
} }
@@ -50,55 +55,76 @@ final class PveProvider extends ChangeNotifier {
); );
final data = ValueNotifier<PveRes?>(null); final data = ValueNotifier<PveRes?>(null);
bool get onlyOneNode => data.value?.nodes.length == 1; bool get onlyOneNode => data.value?.nodes.length == 1;
String? release; String? release;
bool isBusy = false; bool isBusy = false;
// int _localPort = 0;
// String get addr => 'http://127.0.0.1:$_localPort';
Future<void> _init() async { Future<void> _init() async {
try { try {
//await _forward(); await _forward();
await _login(); await _login();
await _release; await _getRelease();
} on PveErr { } on PveErr {
err.value = l10n.pveLoginFailed; err.value = l10n.pveLoginFailed;
} catch (e) { } catch (e, s) {
Loggers.app.warning('PVE init failed', e); Loggers.app.warning('PVE init failed', e, s);
err.value = e.toString(); err.value = e.toString();
} finally { } finally {
connected.complete(); connected.complete();
} }
} }
// Future<void> _forward() async { Future<void> _forward() async {
// var retries = 0; final url = Uri.parse(addr);
// while (retries < 3) { if (_localPort == 0) {
// try { _serverSocket = await ServerSocket.bind('localhost', 0);
// _localPort = Random().nextInt(1000) + 37000; _localPort = _serverSocket.port;
// print('Forwarding local port $_localPort'); _serverSocket.listen((socket) async {
// final serverSocket = await ServerSocket.bind('localhost', _localPort); final forward = await _client.forwardLocal(url.host, url.port);
// final forward = await _client.forwardLocal('127.0.0.1', 8006); _forwards.add(forward);
// serverSocket.listen((socket) { forward.stream.cast<List<int>>().pipe(socket);
// forward.stream.cast<List<int>>().pipe(socket); socket.cast<List<int>>().pipe(forward.sink);
// socket.pipe(forward.sink); });
// }); final newUrl = Uri.parse(addr)
// return; .replace(host: 'localhost', port: _localPort)
// } on SocketException { .toString();
// retries++; debugPrint('Forwarding $newUrl to $addr');
// } }
// } }
// throw Exception('Failed to bind local port');
// } Future<ConnectionTask<Socket>> cf(
Uri url, String? proxyHost, int? proxyPort) async {
/* final serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, 0);
final _localPort = serverSocket.port;
serverSocket.listen((socket) async {
final forward = await _client.forwardLocal(url.host, url.port);
forwards.add(forward);
forward.stream.cast<List<int>>().pipe(socket);
socket.cast<List<int>>().pipe(forward.sink);
});*/
if (url.isScheme("https")) {
return SecureSocket.startConnect('localhost', _localPort,
onBadCertificate: (_) => true);
} else {
return Socket.startConnect('localhost', _localPort);
}
}
Future<void> _login() async { Future<void> _login() async {
final resp = await session.post('$addr/api2/extjs/access/ticket', data: { final resp = await session.post(
'username': spi.user, '$addr/api2/extjs/access/ticket',
'password': spi.pwd, data: {
'realm': 'pam', 'username': spi.user,
'new-format': '1' 'password': spi.pwd,
}); 'realm': 'pam',
'new-format': '1'
},
options: Options(
headers: {HttpHeaders.contentTypeHeader: Headers.jsonContentType},
),
);
try { try {
final ticket = resp.data['data']['ticket']; final ticket = resp.data['data']['ticket'];
session.options.headers['CSRFPreventionToken'] = session.options.headers['CSRFPreventionToken'] =
@@ -110,7 +136,7 @@ final class PveProvider extends ChangeNotifier {
} }
/// Returns true if the PVE version is 8.0 or later /// Returns true if the PVE version is 8.0 or later
Future<void> get _release async { Future<void> _getRelease() async {
final resp = await session.get('$addr/api2/extjs/version'); final resp = await session.get('$addr/api2/extjs/version');
final version = resp.data['data']['release'] as String?; final version = resp.data['data']['release'] as String?;
if (version != null) { if (version != null) {
@@ -167,4 +193,13 @@ final class PveProvider extends ChangeNotifier {
bool _isCtrlSuc(Response resp) { bool _isCtrlSuc(Response resp) {
return resp.statusCode == 200; return resp.statusCode == 200;
} }
@override
Future<void> dispose() async {
super.dispose();
await _serverSocket.close();
for (final forward in _forwards) {
forward.close();
}
}
} }

View File

@@ -5,20 +5,19 @@ import 'package:computer/computer.dart';
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:toolbox/core/extension/ssh_client.dart'; import 'package:server_box/core/extension/ssh_client.dart';
import 'package:toolbox/core/utils/ssh_auth.dart'; import 'package:server_box/core/utils/ssh_auth.dart';
import 'package:toolbox/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:toolbox/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:toolbox/data/model/sftp/req.dart'; import 'package:server_box/data/model/sftp/req.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import '../../core/utils/server.dart'; import '../../core/utils/server.dart';
import '../model/server/server.dart'; import '../model/server/server.dart';
import '../model/server/server_private_info.dart'; import '../model/server/server_private_info.dart';
import '../model/server/server_status_update_req.dart'; import '../model/server/server_status_update_req.dart';
import '../model/server/snippet.dart';
import '../model/server/try_limiter.dart'; import '../model/server/try_limiter.dart';
import '../res/status.dart'; import '../res/status.dart';
@@ -460,20 +459,20 @@ class ServerProvider extends ChangeNotifier {
TryLimiter.reset(sid); TryLimiter.reset(sid);
} }
Future<SnippetResult?> runSnippet(String id, Snippet snippet) async { // Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
final server = _servers[id]; // final server = _servers[id];
if (server == null) return null; // if (server == null) return null;
final watch = Stopwatch()..start(); // final watch = Stopwatch()..start();
final result = await server.client?.run(snippet.fmtWith(server.spi)).string; // final result = await server.client?.run(snippet.fmtWithArgs(server.spi)).string;
final time = watch.elapsed; // final time = watch.elapsed;
watch.stop(); // watch.stop();
if (result == null) return null; // if (result == null) return null;
return SnippetResult( // return SnippetResult(
dest: _servers[id]?.spi.name, // dest: _servers[id]?.spi.name,
result: result, // result: result,
time: time, // time: time,
); // );
} // }
// Future<List<SnippetResult?>> runSnippetsMulti( // Future<List<SnippetResult?>> runSnippetsMulti(
// List<String> ids, // List<String> ids,

View File

@@ -2,8 +2,8 @@ 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:toolbox/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
class SnippetProvider extends ChangeNotifier { class SnippetProvider extends ChangeNotifier {
late List<Snippet> _snippets; late List<Snippet> _snippets;

View File

@@ -1,5 +1,5 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:xterm/core.dart'; import 'package:xterm/core.dart';
class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier { class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {

View File

@@ -1,10 +1,7 @@
// This file is generated by make script. Do not edit. // This file is generated by fl_build. Do not edit.
class BuildData { class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 918; static const int build = 992;
static const String engine = "3.22.1"; static const int script = 49;
static const String buildAt = "2024-05-25 19:17:18";
static const int modifications = 2;
static const int script = 48;
} }

View File

@@ -1,19 +1,19 @@
import 'package:toolbox/data/model/app/github_id.dart';
abstract final class GithubIds { abstract final class GithubIds {
// Thanks // Thanks
// If you want to change your Github ID, please open an issue. // If you want to change your Github ID, please open an issue.
static const contributors = <GhId>{ static const contributors = <GhId>{
'PaperCube', 'PaperCube',
'Integral-Tech',
'its-tom', 'its-tom',
'leganck',
'azkadev', 'azkadev',
'kalashnikov', 'kalashnikov',
'FrancXPT',
'RainSunMe',
'calvinweb', 'calvinweb',
'QazCetelic',
'RainSunMe',
'FrancXPT',
'Liloupar', 'Liloupar',
'dccif', 'dccif',
'QazCetelic',
}; };
static const participants = <GhId>{ static const participants = <GhId>{
@@ -73,5 +73,22 @@ abstract final class GithubIds {
'FHU-yezi', 'FHU-yezi',
'ZRY233', 'ZRY233',
'Jasonzhu1207', 'Jasonzhu1207',
'sakuraanzu',
'licaon-kter',
'77160860',
'mijjjj',
'muyunil',
'Hua159',
'jaydong2016',
'geol',
'Mooling0602',
'IllTamer',
'marlkiller',
}; };
} }
typedef GhId = String;
extension GhIdX on GhId {
String get url => 'https://github.com/$this';
}

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
abstract final class Miscs { abstract final class Miscs {
static final blankReg = RegExp(r'\s+'); static final blankReg = RegExp(r'\s+');
static final multiBlankreg = RegExp(r'\s{2,}');
/// RegExp for password request /// RegExp for password request
static final pwdRequestWithUserReg = RegExp(r'\[sudo\] password for (.+):'); static final pwdRequestWithUserReg = RegExp(r'\[sudo\] password for (.+):');
@@ -18,4 +19,6 @@ abstract final class Miscs {
static const pkgName = 'tech.lolli.toolbox'; static const pkgName = 'tech.lolli.toolbox';
static const jsonEncoder = JsonEncoder.withIndent(' '); static const jsonEncoder = JsonEncoder.withIndent(' ');
static const bakFileName = 'srvbox_bak.json';
} }

View File

@@ -1,9 +1,9 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/provider/app.dart'; import 'package:server_box/data/provider/app.dart';
import 'package:toolbox/data/provider/private_key.dart'; import 'package:server_box/data/provider/private_key.dart';
import 'package:toolbox/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
import 'package:toolbox/data/provider/sftp.dart'; import 'package:server_box/data/provider/sftp.dart';
import 'package:toolbox/data/provider/snippet.dart'; import 'package:server_box/data/provider/snippet.dart';
abstract final class Pros { abstract final class Pros {
static final app = AppProvider(); static final app = AppProvider();

View File

@@ -1,5 +1,6 @@
import 'package:toolbox/data/model/app/rebuild.dart'; import 'package:fl_lib/fl_lib.dart';
abstract final class RebuildNodes { abstract final class RNodes {
static final app = RebuildNode(); static final app = RNode();
static final dark = false.vn;
} }

View File

@@ -1,5 +1,5 @@
import 'package:toolbox/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:toolbox/data/model/server/temp.dart'; import 'package:server_box/data/model/server/temp.dart';
import '../model/server/cpu.dart'; import '../model/server/cpu.dart';
import '../model/server/disk.dart'; import '../model/server/disk.dart';

View File

@@ -1,10 +1,10 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/store/container.dart'; import 'package:server_box/data/store/container.dart';
import 'package:toolbox/data/store/history.dart'; import 'package:server_box/data/store/history.dart';
import 'package:toolbox/data/store/private_key.dart'; import 'package:server_box/data/store/private_key.dart';
import 'package:toolbox/data/store/server.dart'; import 'package:server_box/data/store/server.dart';
import 'package:toolbox/data/store/setting.dart'; import 'package:server_box/data/store/setting.dart';
import 'package:toolbox/data/store/snippet.dart'; import 'package:server_box/data/store/snippet.dart';
abstract final class Stores { abstract final class Stores {
static final setting = SettingStore(); static final setting = SettingStore();

View File

@@ -1,8 +1,9 @@
abstract final class Urls { abstract final class Urls {
static const cdnBase = 'https://cdn.lolli.tech/serverbox'; static const cdnBase = 'https://cdn.lolli.tech/serverbox';
static const updateCfg = '$cdnBase/update.json'; static const updateCfg = '$cdnBase/update2.json';
static const myGithub = 'https://github.com/lollipopkit'; static const myGithub = 'https://github.com/lollipopkit';
static const appHelp = '$myGithub/flutter_server_box#-help'; static const thisRepo = '$myGithub/flutter_server_box';
static const appWiki = '$myGithub/flutter_server_box/wiki'; static const appHelp = '$thisRepo#-help';
static const appWiki = '$thisRepo/wiki';
static const analysis = 'https://countly.lolli.tech'; static const analysis = 'https://countly.lolli.tech';
} }

View File

@@ -1,6 +1,6 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/model/container/type.dart'; import 'package:server_box/data/model/container/type.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
const _keyConfig = 'providerConfig'; const _keyConfig = 'providerConfig';

View File

@@ -54,4 +54,7 @@ class HistoryStore extends PersistentStore {
late final sftpLastPath = _MapHistory(box: box, name: 'sftpLastPath'); late final sftpLastPath = _MapHistory(box: box, name: 'sftpLastPath');
late final sshCmds = _ListHistory(box: box, name: 'sshCmds'); late final sshCmds = _ListHistory(box: box, name: 'sshCmds');
/// Notify users that this app will write script to server to works properly
late final writeScriptTipShown = property('writeScriptTipShown', false);
} }

View File

@@ -1,7 +1,7 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:toolbox/data/model/app/menu/server_func.dart'; import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:toolbox/data/model/app/server_detail_card.dart'; import 'package:server_box/data/model/app/server_detail_card.dart';
import 'package:toolbox/data/model/ssh/virtual_key.dart'; import 'package:server_box/data/model/ssh/virtual_key.dart';
import '../model/app/net_view.dart'; import '../model/app/net_view.dart';
import '../res/default.dart'; import '../res/default.dart';
@@ -265,8 +265,6 @@ class SettingStore extends PersistentStore {
late final horizonVirtKey = property('horizonVirtKey', false); late final horizonVirtKey = property('horizonVirtKey', false);
late final collectUsage = property('collectUsage', true);
/// general wake lock /// general wake lock
late final generalWakeLock = property('generalWakeLock', false); late final generalWakeLock = property('generalWakeLock', false);
@@ -276,6 +274,14 @@ class SettingStore extends PersistentStore {
/// fmt: https://example.com/{DIST}-{BRIGHT}.png /// fmt: https://example.com/{DIST}-{BRIGHT}.png
late final serverLogoUrl = property('serverLogoUrl', ''); late final serverLogoUrl = property('serverLogoUrl', '');
late final betaTest = property('betaTest', false);
/// If it's empty, skip change window size.
/// Format: {width}x{height}
late final windowSize = property('windowSize', '');
late final showIntro = property('showIntro', true);
// Never show these settings for users // Never show these settings for users
// //
// ------BEGIN------ // ------BEGIN------

107
lib/intro.dart Normal file
View File

@@ -0,0 +1,107 @@
part of 'app.dart';
final class _IntroPage extends StatelessWidget {
const _IntroPage();
static final _setting = Stores.setting;
static const _kIconSize = 23.0;
static const _introListPad = EdgeInsets.symmetric(horizontal: 17);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, cons) {
final padTop = cons.maxHeight * .2;
return IntroPage(
pages: [
_buildAppSettings(context, padTop),
_buildRecommended(context, padTop),
],
onDone: (ctx) {
Stores.setting.showIntro.put(false);
Navigator.of(ctx).pushReplacement(
MaterialPageRoute(builder: (_) => const HomePage()),
);
},
);
},
);
}
Widget _buildRecommended(BuildContext context, double padTop) {
return ListView(
padding: _introListPad,
children: [
SizedBox(height: padTop),
const Icon(Bootstrap.stars, size: 35),
SizedBox(height: padTop),
ListTile(
leading: const Icon(MingCute.delete_2_fill),
title: const Text('rm -r'),
subtitle: Text(l10n.sftpRmrDirSummary, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.sftpRmrDir),
).cardx,
ListTile(
leading: const Icon(IonIcons.stats_chart, size: _kIconSize),
title: Text(l10n.parseContainerStats),
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.containerParseStat),
).cardx,
ListTile(
leading: const Icon(OctIcons.cpu),
title: Text('CPU ${l10n.noLineChart}'),
subtitle: Text(l10n.cpuViewAsProgressTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.cpuViewAsProgress),
).cardx,
],
);
}
Widget _buildAppSettings(BuildContext ctx, double padTop) {
return ListView(
padding: _introListPad,
children: [
SizedBox(height: padTop),
_buildTitle(l10n.init, big: true),
SizedBox(height: padTop),
ListTile(
leading: const Icon(IonIcons.language),
title: Text(l10n.language),
onTap: () async {
final selected = await ctx.showPickSingleDialog(
title: l10n.language,
items: AppLocalizations.supportedLocales,
name: (p0) => p0.code,
initial: _setting.locale.fetch().toLocale,
);
if (selected != null) {
_setting.locale.put(selected.code);
RNodes.app.build();
}
},
trailing: Text(
l10n.languageName,
style: const TextStyle(fontSize: 15, color: Colors.grey),
),
).cardx,
ListTile(
leading: const Icon(Icons.update),
title: Text(l10n.autoCheckUpdate),
subtitle: Text(l10n.fdroidReleaseTip, style: UIs.textGrey),
trailing: StoreSwitch(prop: _setting.autoCheckAppUpdate),
).cardx,
],
);
}
Widget _buildTitle(String text, {bool big = false}) {
return Center(
child: Text(
text,
style: big
? const TextStyle(fontSize: 41, fontWeight: FontWeight.w500)
: UIs.textGrey,
),
);
}
}

View File

@@ -2,6 +2,7 @@
"@@locale": "de", "@@locale": "de",
"about": "Über", "about": "Über",
"aboutThanks": "Vielen Dank an die folgenden Personen, die daran teilgenommen haben.\n", "aboutThanks": "Vielen Dank an die folgenden Personen, die daran teilgenommen haben.\n",
"acceptBeta": "Akzeptieren Sie Testversion-Updates",
"add": "Neu", "add": "Neu",
"addAServer": "Server hinzufügen", "addAServer": "Server hinzufügen",
"addPrivateKey": "Private key hinzufügen", "addPrivateKey": "Private key hinzufügen",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Wenn das Terminal ein sicheres Tastenfeld öffnet, können Sie es aktivieren.", "cnKeyboardCompTip": "Wenn das Terminal ein sicheres Tastenfeld öffnet, können Sie es aktivieren.",
"collapseUI": "Zusammenbrechen", "collapseUI": "Zusammenbrechen",
"collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht", "collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht",
"collectUsage": "Nutzungsinformationen sammeln (unabhängig von der Privatsphäre).",
"conn": "Verbindung", "conn": "Verbindung",
"connected": "in Verbindung gebracht", "connected": "in Verbindung gebracht",
"container": "Container", "container": "Container",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "System verfolgen", "followSystem": "System verfolgen",
"font": "Schriftarten", "font": "Schriftarten",
"fontSize": "Schriftgröße", "fontSize": "Schriftgröße",
"forExample": "Zum Beispiel",
"force": "freiwillig", "force": "freiwillig",
"foundNUpdate": "Update {count} gefunden", "foundNUpdate": "Update {count} gefunden",
"fullScreen": "Vollbildmodus", "fullScreen": "Vollbildmodus",
@@ -133,6 +136,7 @@
"imagesList": "Images", "imagesList": "Images",
"import": "Importieren", "import": "Importieren",
"inAppUpdate": "Im App aktualisieren? Andernfalls mit einem Browser herunterladen.", "inAppUpdate": "Im App aktualisieren? Andernfalls mit einem Browser herunterladen.",
"init": "Initialisieren",
"inner": "Eingebaut", "inner": "Eingebaut",
"inputDomainHere": "Domain eingeben", "inputDomainHere": "Domain eingeben",
"install": "install", "install": "install",
@@ -229,6 +233,7 @@
"rememberChoice": "Auswahl merken", "rememberChoice": "Auswahl merken",
"rememberPwdInMem": "Passwort im Speicher behalten", "rememberPwdInMem": "Passwort im Speicher behalten",
"rememberPwdInMemTip": "Für Container, Aufhängen usw.", "rememberPwdInMemTip": "Für Container, Aufhängen usw.",
"rememberWindowSize": "Fenstergröße merken",
"remotePath": "Entfernte Pfade", "remotePath": "Entfernte Pfade",
"rename": "Umbenennen", "rename": "Umbenennen",
"reportBugsOnGithubIssue": "Bitte Bugs auf {url} melden", "reportBugsOnGithubIssue": "Bitte Bugs auf {url} melden",
@@ -280,6 +285,7 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.", "suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
"switchTo": "Wechseln zu {val}", "switchTo": "Wechseln zu {val}",
"sync": "Sync",
"syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.", "syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.",
"system": "Systeme", "system": "Systeme",
"tag": "Tags", "tag": "Tags",

View File

@@ -2,6 +2,7 @@
"@@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",
"add": "Add", "add": "Add",
"addAServer": "add a server", "addAServer": "add a server",
"addPrivateKey": "Add private key", "addPrivateKey": "Add private key",
@@ -25,6 +26,7 @@
"backupTip": "The exported data is simply encrypted. \nPlease keep it safe.", "backupTip": "The exported data is simply 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 backgroud", "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 native Android, please disable \"Battery Optimization\" in this app, and for miui, 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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "If the terminal pops up a secure keyboard, you can enable it.", "cnKeyboardCompTip": "If the terminal pops up a secure keyboard, you can enable it.",
"collapseUI": "Collapse", "collapseUI": "Collapse",
"collapseUITip": "Whether to collapse long lists present in the UI by default", "collapseUITip": "Whether to collapse long lists present in the UI by default",
"collectUsage": "Collect usage information (unrelated to privacy).",
"conn": "Connection", "conn": "Connection",
"connected": "Connected", "connected": "Connected",
"container": "Container", "container": "Container",
@@ -100,6 +101,7 @@
"export": "Export", "export": "Export",
"extraArgs": "Extra args", "extraArgs": "Extra args",
"failed": "Failed", "failed": "Failed",
"fdroidReleaseTip": "If you downloaded this app from Fdroid, 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 feedback on Github.",
"fieldMustNotEmpty": "These fields must not be empty.", "fieldMustNotEmpty": "These fields must not be empty.",
@@ -110,6 +112,7 @@
"followSystem": "Follow system", "followSystem": "Follow system",
"font": "Font", "font": "Font",
"fontSize": "Font size", "fontSize": "Font size",
"forExample": "For example",
"force": "Force", "force": "Force",
"foundNUpdate": "Found {count} update", "foundNUpdate": "Found {count} update",
"fullScreen": "Full screen mode", "fullScreen": "Full screen mode",
@@ -133,6 +136,7 @@
"imagesList": "Images list", "imagesList": "Images list",
"import": "Import", "import": "Import",
"inAppUpdate": "Update within the app? Otherwise, download using a browser.", "inAppUpdate": "Update within the app? Otherwise, download using a browser.",
"init": "Initialize",
"inner": "Inner", "inner": "Inner",
"inputDomainHere": "Input Domain here", "inputDomainHere": "Input Domain here",
"install": "install", "install": "install",
@@ -229,6 +233,7 @@
"rememberChoice": "Remember the selection", "rememberChoice": "Remember the selection",
"rememberPwdInMem": "Remember password in memory", "rememberPwdInMem": "Remember password in memory",
"rememberPwdInMemTip": "Used for containers, suspending, etc.", "rememberPwdInMemTip": "Used for containers, suspending, etc.",
"rememberWindowSize": "Remember window size",
"remotePath": "Remote path", "remotePath": "Remote path",
"rename": "Rename", "rename": "Rename",
"reportBugsOnGithubIssue": "Please report bugs on {url}", "reportBugsOnGithubIssue": "Please report bugs on {url}",
@@ -280,6 +285,7 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "The suspend function requires root privileges and systemd support.", "suspendTip": "The suspend function requires root privileges and systemd support.",
"switchTo": "Switch to {val}", "switchTo": "Switch to {val}",
"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.",
"system": "System", "system": "System",
"tag": "Tags", "tag": "Tags",

View File

@@ -2,6 +2,7 @@
"@@locale": "es", "@@locale": "es",
"about": "Acerca de", "about": "Acerca de",
"aboutThanks": "Gracias a los siguientes participantes.", "aboutThanks": "Gracias a los siguientes participantes.",
"acceptBeta": "Aceptar actualizaciones de la versión de prueba",
"add": "Añadir", "add": "Añadir",
"addAServer": "Agregar un servidor", "addAServer": "Agregar un servidor",
"addPrivateKey": "Agregar una llave privada", "addPrivateKey": "Agregar una llave privada",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Si el terminal muestra un teclado seguro, puedes activarlo.", "cnKeyboardCompTip": "Si el terminal muestra un teclado seguro, puedes activarlo.",
"collapseUI": "Colapsar", "collapseUI": "Colapsar",
"collapseUITip": "¿Colapsar por defecto las listas largas en la UI?", "collapseUITip": "¿Colapsar por defecto las listas largas en la UI?",
"collectUsage": "Recopilar información de uso (no relacionada con la privacidad).",
"conn": "Conectar", "conn": "Conectar",
"connected": "Conectado", "connected": "Conectado",
"container": "Contenedor", "container": "Contenedor",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "Seguir al sistema", "followSystem": "Seguir al sistema",
"font": "Fuente", "font": "Fuente",
"fontSize": "Tamaño de fuente", "fontSize": "Tamaño de fuente",
"forExample": "Por ejemplo",
"force": "Forzar", "force": "Forzar",
"foundNUpdate": "Encontradas {count} actualizaciones", "foundNUpdate": "Encontradas {count} actualizaciones",
"fullScreen": "Modo pantalla completa", "fullScreen": "Modo pantalla completa",
@@ -133,6 +136,7 @@
"imagesList": "Lista de imágenes", "imagesList": "Lista de imágenes",
"import": "Importar", "import": "Importar",
"inAppUpdate": "¿Actualizar dentro de la app? De lo contrario, descargar usando un navegador.", "inAppUpdate": "¿Actualizar dentro de la app? De lo contrario, descargar usando un navegador.",
"init": "Inicializar",
"inner": "Interno", "inner": "Interno",
"inputDomainHere": "Introduce el dominio aquí", "inputDomainHere": "Introduce el dominio aquí",
"install": "Instalar", "install": "Instalar",
@@ -229,6 +233,7 @@
"rememberChoice": "Recordar la selección", "rememberChoice": "Recordar la selección",
"rememberPwdInMem": "Recordar contraseña en la memoria", "rememberPwdInMem": "Recordar contraseña en la memoria",
"rememberPwdInMemTip": "Utilizado para contenedores, suspensión, etc.", "rememberPwdInMemTip": "Utilizado para contenedores, suspensión, etc.",
"rememberWindowSize": "Recordar el tamaño de la ventana",
"remotePath": "Ruta remota", "remotePath": "Ruta remota",
"rename": "Renombrar", "rename": "Renombrar",
"reportBugsOnGithubIssue": "Por favor, informa los problemas en {url}", "reportBugsOnGithubIssue": "Por favor, informa los problemas en {url}",
@@ -280,6 +285,7 @@
"suspend": "Suspender", "suspend": "Suspender",
"suspendTip": "La función de suspender necesita permisos de root y soporte de systemd.", "suspendTip": "La función de suspender necesita permisos de root y soporte de systemd.",
"switchTo": "Cambiar a {val}", "switchTo": "Cambiar a {val}",
"sync": "Sincronizar",
"syncTip": "Puede que necesites reiniciar para que algunos cambios tengan efecto.", "syncTip": "Puede que necesites reiniciar para que algunos cambios tengan efecto.",
"system": "Sistema", "system": "Sistema",
"tag": "Etiqueta", "tag": "Etiqueta",

View File

@@ -2,6 +2,7 @@
"@@locale": "fr", "@@locale": "fr",
"about": "À propos", "about": "À propos",
"aboutThanks": "Merci aux personnes suivantes qui ont participé.", "aboutThanks": "Merci aux personnes suivantes qui ont participé.",
"acceptBeta": "Accepter les mises à jour de la version de test",
"add": "Ajouter", "add": "Ajouter",
"addAServer": "Ajouter un serveur", "addAServer": "Ajouter un serveur",
"addPrivateKey": "Ajouter une clé privée", "addPrivateKey": "Ajouter une clé privée",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Si le terminal affiche un clavier sécurisé, vous pouvez l'activer.", "cnKeyboardCompTip": "Si le terminal affiche un clavier sécurisé, vous pouvez l'activer.",
"collapseUI": "Réduire", "collapseUI": "Réduire",
"collapseUITip": "Indique si les longues listes présentées dans l'interface utilisateur doivent être réduites par défaut.", "collapseUITip": "Indique si les longues listes présentées dans l'interface utilisateur doivent être réduites par défaut.",
"collectUsage": "Collecter des informations d'utilisation (sans rapport avec la confidentialité).",
"conn": "Connexion", "conn": "Connexion",
"connected": "Connecté", "connected": "Connecté",
"container": "Conteneur", "container": "Conteneur",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"font": "Police", "font": "Police",
"fontSize": "Taille de la police", "fontSize": "Taille de la police",
"forExample": "Par exemple",
"force": "Forcer", "force": "Forcer",
"foundNUpdate": "{count} mise à jour trouvée", "foundNUpdate": "{count} mise à jour trouvée",
"fullScreen": "Mode plein écran", "fullScreen": "Mode plein écran",
@@ -133,6 +136,7 @@
"imagesList": "Liste des images", "imagesList": "Liste des images",
"import": "Importer", "import": "Importer",
"inAppUpdate": "Mettre à jour dans l'application ? Sinon, téléchargez en utilisant un navigateur.", "inAppUpdate": "Mettre à jour dans l'application ? Sinon, téléchargez en utilisant un navigateur.",
"init": "Initialiser",
"inner": "Interne", "inner": "Interne",
"inputDomainHere": "Saisissez le domaine ici", "inputDomainHere": "Saisissez le domaine ici",
"install": "Installer", "install": "Installer",
@@ -229,6 +233,7 @@
"rememberChoice": "Se souvenir du choix", "rememberChoice": "Se souvenir du choix",
"rememberPwdInMem": "Mémoriser le mot de passe en mémoire", "rememberPwdInMem": "Mémoriser le mot de passe en mémoire",
"rememberPwdInMemTip": "Utilisé pour les conteneurs, la suspension, etc.", "rememberPwdInMemTip": "Utilisé pour les conteneurs, la suspension, etc.",
"rememberWindowSize": "Se souvenir de la taille de la fenêtre",
"remotePath": "Chemin distant", "remotePath": "Chemin distant",
"rename": "Renommer", "rename": "Renommer",
"reportBugsOnGithubIssue": "Veuillez signaler les bugs sur {url}", "reportBugsOnGithubIssue": "Veuillez signaler les bugs sur {url}",
@@ -280,6 +285,7 @@
"suspend": "Suspendre", "suspend": "Suspendre",
"suspendTip": "La fonction de suspension nécessite des privilèges root et le support de systemd.", "suspendTip": "La fonction de suspension nécessite des privilèges root et le support de systemd.",
"switchTo": "Passer à {val}", "switchTo": "Passer à {val}",
"sync": "Sync",
"syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.", "syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.",
"system": "Système", "system": "Système",
"tag": "Étiquettes", "tag": "Étiquettes",

View File

@@ -2,6 +2,7 @@
"@@locale": "id", "@@locale": "id",
"about": "Tentang", "about": "Tentang",
"aboutThanks": "Terima kasih kepada orang -orang berikut yang berpartisipasi.", "aboutThanks": "Terima kasih kepada orang -orang berikut yang berpartisipasi.",
"acceptBeta": "Terima pembaruan versi uji coba",
"add": "Menambahkan", "add": "Menambahkan",
"addAServer": "tambahkan server", "addAServer": "tambahkan server",
"addPrivateKey": "Tambahkan kunci pribadi", "addPrivateKey": "Tambahkan kunci pribadi",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Jika terminal munculkan keyboard aman, Anda bisa mengaktifkannya.", "cnKeyboardCompTip": "Jika terminal munculkan keyboard aman, Anda bisa mengaktifkannya.",
"collapseUI": "Runtuh", "collapseUI": "Runtuh",
"collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak", "collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak",
"collectUsage": "Mengumpulkan informasi penggunaan (tidak terkait dengan privasi).",
"conn": "Koneksi", "conn": "Koneksi",
"connected": "Terhubung", "connected": "Terhubung",
"container": "Wadah", "container": "Wadah",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "Ikuti sistem", "followSystem": "Ikuti sistem",
"font": "Font", "font": "Font",
"fontSize": "Ukuran huruf", "fontSize": "Ukuran huruf",
"forExample": "Sebagai contoh",
"force": "sukarela", "force": "sukarela",
"foundNUpdate": "Menemukan {count} pembaruan", "foundNUpdate": "Menemukan {count} pembaruan",
"fullScreen": "Mode Layar Penuh", "fullScreen": "Mode Layar Penuh",
@@ -133,6 +136,7 @@
"imagesList": "Daftar gambar", "imagesList": "Daftar gambar",
"import": "Impor", "import": "Impor",
"inAppUpdate": "Perbarui di dalam aplikasi? Jika tidak, unduh menggunakan browser.", "inAppUpdate": "Perbarui di dalam aplikasi? Jika tidak, unduh menggunakan browser.",
"init": "Menginisialisasi",
"inner": "Batin", "inner": "Batin",
"inputDomainHere": "Input domain di sini", "inputDomainHere": "Input domain di sini",
"install": "Install", "install": "Install",
@@ -229,6 +233,7 @@
"rememberChoice": "Ingat pilihan", "rememberChoice": "Ingat pilihan",
"rememberPwdInMem": "Ingat kata sandi di dalam memori", "rememberPwdInMem": "Ingat kata sandi di dalam memori",
"rememberPwdInMemTip": "Digunakan untuk kontainer, menangguhkan, dll.", "rememberPwdInMemTip": "Digunakan untuk kontainer, menangguhkan, dll.",
"rememberWindowSize": "Ingat ukuran jendela",
"remotePath": "Jalur jarak jauh", "remotePath": "Jalur jarak jauh",
"rename": "Ganti nama", "rename": "Ganti nama",
"reportBugsOnGithubIssue": "Harap laporkan bug di {url}", "reportBugsOnGithubIssue": "Harap laporkan bug di {url}",
@@ -280,6 +285,7 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.", "suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
"switchTo": "Beralih ke {val}", "switchTo": "Beralih ke {val}",
"sync": "Sinkronisasi",
"syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.", "syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.",
"system": "Sistem", "system": "Sistem",
"tag": "Tag", "tag": "Tag",

View File

@@ -2,6 +2,7 @@
"@@locale": "ja", "@@locale": "ja",
"about": "約", "about": "約",
"aboutThanks": "以下の参加者に感謝します。", "aboutThanks": "以下の参加者に感謝します。",
"acceptBeta": "テストバージョンの更新を受け入れる",
"add": "追加", "add": "追加",
"addAServer": "サーバーを追加する", "addAServer": "サーバーを追加する",
"addPrivateKey": "プライベートキーを追加", "addPrivateKey": "プライベートキーを追加",
@@ -25,6 +26,7 @@
"backupTip": "エクスポートされたデータは簡単に暗号化されています。適切に保管してください。", "backupTip": "エクスポートされたデータは簡単に暗号化されています。適切に保管してください。",
"backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません", "backupVersionNotMatch": "バックアップバージョンが一致しないため、復元できません",
"battery": "バッテリー", "battery": "バッテリー",
"beforeConnect": "ServerBoxは接続後に`~/.config/server_box`にスクリプトを書き込み実行します。詳細な技術情報については[Github]({url})をご覧ください。",
"bgRun": "バックグラウンド実行", "bgRun": "バックグラウンド実行",
"bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。ネイティブAndroidでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。", "bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。ネイティブAndroidでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。",
"bioAuth": "生体認証", "bioAuth": "生体認証",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "ターミナルがセキュアキーボードを表示した場合、それを有効にできます。", "cnKeyboardCompTip": "ターミナルがセキュアキーボードを表示した場合、それを有効にできます。",
"collapseUI": "UIを折りたたむ", "collapseUI": "UIを折りたたむ",
"collapseUITip": "UIの長いリストをデフォルトで折りたたむかどうか", "collapseUITip": "UIの長いリストをデフォルトで折りたたむかどうか",
"collectUsage": "使用情報を収集する(プライバシーとは関係ない)。",
"conn": "接続", "conn": "接続",
"connected": "接続済み", "connected": "接続済み",
"container": "コンテナ", "container": "コンテナ",
@@ -100,6 +101,7 @@
"export": "エクスポート", "export": "エクスポート",
"extraArgs": "追加引数", "extraArgs": "追加引数",
"failed": "失敗しました", "failed": "失敗しました",
"fdroidReleaseTip": "このアプリをFdroidからダウンロードした場合、このオプションをオフにすることをお勧めします。",
"feedback": "フィードバック", "feedback": "フィードバック",
"feedbackOnGithub": "問題がある場合は、GitHubでフィードバックしてください", "feedbackOnGithub": "問題がある場合は、GitHubでフィードバックしてください",
"fieldMustNotEmpty": "これらの入力フィールドは空にできません。", "fieldMustNotEmpty": "これらの入力フィールドは空にできません。",
@@ -110,6 +112,7 @@
"followSystem": "システムに従う", "followSystem": "システムに従う",
"font": "フォント", "font": "フォント",
"fontSize": "フォントサイズ", "fontSize": "フォントサイズ",
"forExample": "例えば",
"force": "強制", "force": "強制",
"foundNUpdate": "{count}個の更新が見つかりました", "foundNUpdate": "{count}個の更新が見つかりました",
"fullScreen": "フルスクリーンモード", "fullScreen": "フルスクリーンモード",
@@ -133,6 +136,7 @@
"imagesList": "イメージリスト", "imagesList": "イメージリスト",
"import": "インポート", "import": "インポート",
"inAppUpdate": "アプリ内で更新しますか?それ以外の場合は、ブラウザを使用してダウンロードしてください。", "inAppUpdate": "アプリ内で更新しますか?それ以外の場合は、ブラウザを使用してダウンロードしてください。",
"init": "初期化する",
"inner": "内蔵", "inner": "内蔵",
"inputDomainHere": "ここにドメインを入力", "inputDomainHere": "ここにドメインを入力",
"install": "インストール", "install": "インストール",
@@ -229,6 +233,7 @@
"rememberChoice": "選択を記憶する", "rememberChoice": "選択を記憶する",
"rememberPwdInMem": "メモリにパスワードを記憶する", "rememberPwdInMem": "メモリにパスワードを記憶する",
"rememberPwdInMemTip": "コンテナ、一時停止などに使用されます。", "rememberPwdInMemTip": "コンテナ、一時停止などに使用されます。",
"rememberWindowSize": "ウィンドウサイズを記憶する",
"remotePath": "リモートパス", "remotePath": "リモートパス",
"rename": "名前を変更", "rename": "名前を変更",
"reportBugsOnGithubIssue": "{url}で問題を報告してください", "reportBugsOnGithubIssue": "{url}で問題を報告してください",
@@ -280,6 +285,7 @@
"suspend": "中断", "suspend": "中断",
"suspendTip": "suspend機能はroot権限とsystemdのサポートが必要です。", "suspendTip": "suspend機能はroot権限とsystemdのサポートが必要です。",
"switchTo": "{val}に切り替える", "switchTo": "{val}に切り替える",
"sync": "同期する",
"syncTip": "再起動が必要な場合があります。一部の変更はその後に有効になります。", "syncTip": "再起動が必要な場合があります。一部の変更はその後に有効になります。",
"system": "システム", "system": "システム",
"tag": "タグ", "tag": "タグ",

View File

@@ -2,6 +2,7 @@
"@@locale": "nl", "@@locale": "nl",
"about": "Over", "about": "Over",
"aboutThanks": "Met dank aan de volgende mensen die hebben deelgenomen aan.", "aboutThanks": "Met dank aan de volgende mensen die hebben deelgenomen aan.",
"acceptBeta": "Accepteer testversie-updates",
"add": "Toevoegen", "add": "Toevoegen",
"addAServer": "een server toevoegen", "addAServer": "een server toevoegen",
"addPrivateKey": "Privésleutel toevoegen", "addPrivateKey": "Privésleutel toevoegen",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Als de terminal een beveiligd toetsenbord weergeeft, kunt u dit inschakelen.", "cnKeyboardCompTip": "Als de terminal een beveiligd toetsenbord weergeeft, kunt u dit inschakelen.",
"collapseUI": "Inklappen", "collapseUI": "Inklappen",
"collapseUITip": "Of lange lijsten in de UI standaard moeten worden ingeklapt", "collapseUITip": "Of lange lijsten in de UI standaard moeten worden ingeklapt",
"collectUsage": "Gebruiksinformatie verzamelen (niet gerelateerd aan privacy).",
"conn": "Verbinding", "conn": "Verbinding",
"connected": "Verbonden", "connected": "Verbonden",
"container": "Container", "container": "Container",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "Volg systeem", "followSystem": "Volg systeem",
"font": "Lettertype", "font": "Lettertype",
"fontSize": "Lettergrootte", "fontSize": "Lettergrootte",
"forExample": "Bijvoorbeeld",
"force": "Forceer", "force": "Forceer",
"foundNUpdate": "{count} update gevonden", "foundNUpdate": "{count} update gevonden",
"fullScreen": "Volledig schermmodus", "fullScreen": "Volledig schermmodus",
@@ -133,6 +136,7 @@
"imagesList": "Lijst met afbeeldingen", "imagesList": "Lijst met afbeeldingen",
"import": "Importeren", "import": "Importeren",
"inAppUpdate": "Bijwerken binnen de app? Anders downloaden via een browser.", "inAppUpdate": "Bijwerken binnen de app? Anders downloaden via een browser.",
"init": "Initialiseren",
"inner": "Intern", "inner": "Intern",
"inputDomainHere": "Voer hier domein in", "inputDomainHere": "Voer hier domein in",
"install": "Installeren", "install": "Installeren",
@@ -176,6 +180,7 @@
"newContainer": "Nieuwe container", "newContainer": "Nieuwe container",
"noClient": "Geen client", "noClient": "Geen client",
"noInterface": "Geen interface", "noInterface": "Geen interface",
"noLineChart": "lijndiagrammen gebruiken",
"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.",
@@ -228,6 +233,7 @@
"rememberChoice": "Selectie onthouden", "rememberChoice": "Selectie onthouden",
"rememberPwdInMem": "Wachtwoord onthouden in geheugen", "rememberPwdInMem": "Wachtwoord onthouden in geheugen",
"rememberPwdInMemTip": "Gebruikt voor containers, opschorting, enz.", "rememberPwdInMemTip": "Gebruikt voor containers, opschorting, enz.",
"rememberWindowSize": "Venstergrootte onthouden",
"remotePath": "Extern pad", "remotePath": "Extern pad",
"rename": "Hernoemen", "rename": "Hernoemen",
"reportBugsOnGithubIssue": "Meld alstublieft bugs op {url}", "reportBugsOnGithubIssue": "Meld alstublieft bugs op {url}",
@@ -279,6 +285,7 @@
"suspend": "Ophangen", "suspend": "Ophangen",
"suspendTip": "De opschortfunctie vereist rootrechten en systemd-ondersteuning.", "suspendTip": "De opschortfunctie vereist rootrechten en systemd-ondersteuning.",
"switchTo": "Overschakelen naar {val}", "switchTo": "Overschakelen naar {val}",
"sync": "Sync",
"syncTip": "Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.", "syncTip": "Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.",
"system": "Systeem", "system": "Systeem",
"tag": "Labels", "tag": "Labels",

View File

@@ -2,6 +2,7 @@
"@@locale": "pt", "@@locale": "pt",
"about": "Sobre", "about": "Sobre",
"aboutThanks": "Agradecimentos a todos os participantes.", "aboutThanks": "Agradecimentos a todos os participantes.",
"acceptBeta": "Aceitar atualizações da versão de teste",
"add": "Adicionar", "add": "Adicionar",
"addAServer": "Adicionar um servidor", "addAServer": "Adicionar um servidor",
"addPrivateKey": "Adicionar uma chave privada", "addPrivateKey": "Adicionar uma chave privada",
@@ -25,6 +26,7 @@
"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",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Se o terminal abrir um teclado seguro, você pode ativá-lo.", "cnKeyboardCompTip": "Se o terminal abrir um teclado seguro, você pode ativá-lo.",
"collapseUI": "Colapsar", "collapseUI": "Colapsar",
"collapseUITip": "Deve colapsar listas longas na UI por padrão?", "collapseUITip": "Deve colapsar listas longas na UI por padrão?",
"collectUsage": "Coletar informações de uso (não relacionadas à privacidade).",
"conn": "Conectar", "conn": "Conectar",
"connected": "Conectado", "connected": "Conectado",
"container": "Contêiner", "container": "Contêiner",
@@ -100,6 +101,7 @@
"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.",
"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.",
@@ -110,6 +112,7 @@
"followSystem": "Seguir sistema", "followSystem": "Seguir sistema",
"font": "Fonte", "font": "Fonte",
"fontSize": "Tamanho da fonte", "fontSize": "Tamanho da fonte",
"forExample": "Por exemplo",
"force": "Forçar", "force": "Forçar",
"foundNUpdate": "Encontradas {count} atualizações", "foundNUpdate": "Encontradas {count} atualizações",
"fullScreen": "Modo tela cheia", "fullScreen": "Modo tela cheia",
@@ -133,6 +136,7 @@
"imagesList": "Lista de imagens", "imagesList": "Lista de imagens",
"import": "Importar", "import": "Importar",
"inAppUpdate": "Atualizar dentro do app? Caso contrário, baixe usando um navegador.", "inAppUpdate": "Atualizar dentro do app? Caso contrário, baixe usando um navegador.",
"init": "Inicializar",
"inner": "Interno", "inner": "Interno",
"inputDomainHere": "Insira o domínio aqui", "inputDomainHere": "Insira o domínio aqui",
"install": "Instalar", "install": "Instalar",
@@ -176,7 +180,7 @@
"newContainer": "Novo contêiner", "newContainer": "Novo contêiner",
"noClient": "Sem conexão SSH", "noClient": "Sem conexão SSH",
"noInterface": "Sem interface disponível", "noInterface": "Sem interface disponível",
"noLineChart": "Gebruik geen lijndiagrammen", "noLineChart": "Não usar gráficos de linha",
"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.",
@@ -229,6 +233,7 @@
"rememberChoice": "Lembrar da seleção", "rememberChoice": "Lembrar da seleção",
"rememberPwdInMem": "Lembrar senha na memória", "rememberPwdInMem": "Lembrar senha na memória",
"rememberPwdInMemTip": "Usado para contêineres, suspensão, etc.", "rememberPwdInMemTip": "Usado para contêineres, suspensão, etc.",
"rememberWindowSize": "Lembrar o tamanho da janela",
"remotePath": "Caminho remoto", "remotePath": "Caminho remoto",
"rename": "Renomear", "rename": "Renomear",
"reportBugsOnGithubIssue": "Por favor, reporte problemas em {url}", "reportBugsOnGithubIssue": "Por favor, reporte problemas em {url}",
@@ -280,6 +285,7 @@
"suspend": "Suspender", "suspend": "Suspender",
"suspendTip": "A função de suspensão requer permissões de root e suporte do systemd.", "suspendTip": "A função de suspensão requer permissões de root e suporte do systemd.",
"switchTo": "Mudar para {val}", "switchTo": "Mudar para {val}",
"sync": "Sincronizar",
"syncTip": "Pode ser necessário reiniciar para algumas mudanças surtirem efeito.", "syncTip": "Pode ser necessário reiniciar para algumas mudanças surtirem efeito.",
"system": "Sistema", "system": "Sistema",
"tag": "Tag", "tag": "Tag",

View File

@@ -2,6 +2,7 @@
"@@locale": "ru", "@@locale": "ru",
"about": "о", "about": "о",
"aboutThanks": "Благодарности всем участникам.", "aboutThanks": "Благодарности всем участникам.",
"acceptBeta": "Принять обновления тестовой версии",
"add": "добавить", "add": "добавить",
"addAServer": "добавить сервер", "addAServer": "добавить сервер",
"addPrivateKey": "добавить приватный ключ", "addPrivateKey": "добавить приватный ключ",
@@ -25,6 +26,7 @@
"backupTip": "Экспортированные данные зашифрованы простым способом, пожалуйста, храните их в безопасности.", "backupTip": "Экспортированные данные зашифрованы простым способом, пожалуйста, храните их в безопасности.",
"backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно", "backupVersionNotMatch": "Версия резервной копии не совпадает, восстановление невозможно",
"battery": "батарея", "battery": "батарея",
"beforeConnect": "ServerBox запишет и выполнит скрипт в `~/.config/server_box` после подключения. Для получения дополнительных технических подробностей, пожалуйста, посетите [Github]({url}).",
"bgRun": "работа в фоновом режиме", "bgRun": "работа в фоновом режиме",
"bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените стратегию энергосбережения на «Без ограничений».", "bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените стратегию энергосбережения на «Без ограничений».",
"bioAuth": "биометрическая аутентификация", "bioAuth": "биометрическая аутентификация",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "Если терминал отображает безопасную клавиатуру, вы можете ее активировать.", "cnKeyboardCompTip": "Если терминал отображает безопасную клавиатуру, вы можете ее активировать.",
"collapseUI": "свернуть", "collapseUI": "свернуть",
"collapseUITip": "Свернуть длинные списки в UI по умолчанию", "collapseUITip": "Свернуть длинные списки в UI по умолчанию",
"collectUsage": "Сбор информации об использовании (не связано с конфиденциальностью).",
"conn": "подключение", "conn": "подключение",
"connected": "подключено", "connected": "подключено",
"container": "контейнер", "container": "контейнер",
@@ -100,6 +101,7 @@
"export": "экспорт", "export": "экспорт",
"extraArgs": "дополнительные аргументы", "extraArgs": "дополнительные аргументы",
"failed": "неудача", "failed": "неудача",
"fdroidReleaseTip": "Если вы скачали это приложение с Fdroid, рекомендуется отключить эту опцию.",
"feedback": "обратная связь", "feedback": "обратная связь",
"feedbackOnGithub": "Если у вас есть какие-либо вопросы, пожалуйста, отправьте отзыв на GitHub", "feedbackOnGithub": "Если у вас есть какие-либо вопросы, пожалуйста, отправьте отзыв на GitHub",
"fieldMustNotEmpty": "Эти поля не могут быть пустыми.", "fieldMustNotEmpty": "Эти поля не могут быть пустыми.",
@@ -110,6 +112,7 @@
"followSystem": "следовать за системой", "followSystem": "следовать за системой",
"font": "шрифт", "font": "шрифт",
"fontSize": "размер шрифта", "fontSize": "размер шрифта",
"forExample": "Например",
"force": "принудительно", "force": "принудительно",
"foundNUpdate": "Найдено {count} обновлений", "foundNUpdate": "Найдено {count} обновлений",
"fullScreen": "полноэкранный режим", "fullScreen": "полноэкранный режим",
@@ -133,6 +136,7 @@
"imagesList": "список образов", "imagesList": "список образов",
"import": "импорт", "import": "импорт",
"inAppUpdate": "Обновить в приложении? В противном случае загрузите с помощью браузера.", "inAppUpdate": "Обновить в приложении? В противном случае загрузите с помощью браузера.",
"init": "Инициализировать",
"inner": "встроенный", "inner": "встроенный",
"inputDomainHere": "введите домен здесь", "inputDomainHere": "введите домен здесь",
"install": "установить", "install": "установить",
@@ -176,7 +180,7 @@
"newContainer": "создать контейнер", "newContainer": "создать контейнер",
"noClient": "нет SSH соединения", "noClient": "нет SSH соединения",
"noInterface": "нет доступных интерфейсов", "noInterface": "нет доступных интерфейсов",
"noLineChart": "Não use gráficos de linha", "noLineChart": "Не использовать линейные графики",
"noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.", "noNotiPerm": "Нет разрешения на уведомления, возможно отсутствие индикации прогресса при загрузке обновлений приложений.",
"noOptions": "нет доступных опций", "noOptions": "нет доступных опций",
"noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.", "noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.",
@@ -229,6 +233,7 @@
"rememberChoice": "Запомнить выбор", "rememberChoice": "Запомнить выбор",
"rememberPwdInMem": "Запомнить пароль в памяти", "rememberPwdInMem": "Запомнить пароль в памяти",
"rememberPwdInMemTip": "Используется для контейнеров, приостановки и т. д.", "rememberPwdInMemTip": "Используется для контейнеров, приостановки и т. д.",
"rememberWindowSize": "Запомнить размер окна",
"remotePath": "удаленный путь", "remotePath": "удаленный путь",
"rename": "переименовать", "rename": "переименовать",
"reportBugsOnGithubIssue": "Пожалуйста, сообщайте о проблемах на {url}", "reportBugsOnGithubIssue": "Пожалуйста, сообщайте о проблемах на {url}",
@@ -280,6 +285,7 @@
"suspend": "приостановить", "suspend": "приостановить",
"suspendTip": "Функция приостановки требует прав root и поддержки systemd.", "suspendTip": "Функция приостановки требует прав root и поддержки systemd.",
"switchTo": "переключиться на {val}", "switchTo": "переключиться на {val}",
"sync": "Синхронизировать",
"syncTip": "Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.", "syncTip": "Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.",
"system": "система", "system": "система",
"tag": "тег", "tag": "тег",

View File

@@ -2,6 +2,7 @@
"@@locale": "zh", "@@locale": "zh",
"about": "关于", "about": "关于",
"aboutThanks": "感谢以下参与的各位。", "aboutThanks": "感谢以下参与的各位。",
"acceptBeta": "接受测试版更新推送",
"add": "新增", "add": "新增",
"addAServer": "添加服务器", "addAServer": "添加服务器",
"addPrivateKey": "添加一个私钥", "addPrivateKey": "添加一个私钥",
@@ -25,6 +26,7 @@
"backupTip": "导出的数据仅进行了简单加密,请妥善保管。", "backupTip": "导出的数据仅进行了简单加密,请妥善保管。",
"backupVersionNotMatch": "备份版本不匹配,无法恢复", "backupVersionNotMatch": "备份版本不匹配,无法恢复",
"battery": "电池", "battery": "电池",
"beforeConnect": "ServerBox 将在连接后,在 `~/.config/server_box` 写入脚本并执行,更多的技术细节请访问 [Github]({url})。",
"bgRun": "后台运行", "bgRun": "后台运行",
"bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI 请修改省电策略为“无限制”。", "bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI 请修改省电策略为“无限制”。",
"bioAuth": "生物认证", "bioAuth": "生物认证",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "如果终端弹出安全键盘,可以开启", "cnKeyboardCompTip": "如果终端弹出安全键盘,可以开启",
"collapseUI": "折叠", "collapseUI": "折叠",
"collapseUITip": "是否默认折叠UI中存在的长列表", "collapseUITip": "是否默认折叠UI中存在的长列表",
"collectUsage": "搜集使用信息(与隐私无关)",
"conn": "连接", "conn": "连接",
"connected": "已连接", "connected": "已连接",
"container": "容器", "container": "容器",
@@ -100,6 +101,7 @@
"export": "导出", "export": "导出",
"extraArgs": "额外参数", "extraArgs": "额外参数",
"failed": "失败", "failed": "失败",
"fdroidReleaseTip": "如果你是从 Fdroid 下载的本应用,推荐关闭此选项",
"feedback": "反馈", "feedback": "反馈",
"feedbackOnGithub": "如果你有任何问题请在GitHub反馈", "feedbackOnGithub": "如果你有任何问题请在GitHub反馈",
"fieldMustNotEmpty": "这些输入框不能为空。", "fieldMustNotEmpty": "这些输入框不能为空。",
@@ -110,6 +112,7 @@
"followSystem": "跟随系统", "followSystem": "跟随系统",
"font": "字体", "font": "字体",
"fontSize": "字体大小", "fontSize": "字体大小",
"forExample": "例如",
"force": "强制", "force": "强制",
"foundNUpdate": "找到 {count} 个更新", "foundNUpdate": "找到 {count} 个更新",
"fullScreen": "全屏模式", "fullScreen": "全屏模式",
@@ -133,6 +136,7 @@
"imagesList": "镜像列表", "imagesList": "镜像列表",
"import": "导入", "import": "导入",
"inAppUpdate": "在App内更新否则使用浏览器下载", "inAppUpdate": "在App内更新否则使用浏览器下载",
"init": "初始化",
"inner": "内置", "inner": "内置",
"inputDomainHere": "在这里输入域名", "inputDomainHere": "在这里输入域名",
"install": "安装", "install": "安装",
@@ -229,6 +233,7 @@
"rememberChoice": "记住选择", "rememberChoice": "记住选择",
"rememberPwdInMem": "在内存中记住密码", "rememberPwdInMem": "在内存中记住密码",
"rememberPwdInMemTip": "用于容器、挂起等", "rememberPwdInMemTip": "用于容器、挂起等",
"rememberWindowSize": "记住窗口大小",
"remotePath": "远端路径", "remotePath": "远端路径",
"rename": "重命名", "rename": "重命名",
"reportBugsOnGithubIssue": "请到 {url} 提交问题", "reportBugsOnGithubIssue": "请到 {url} 提交问题",
@@ -280,6 +285,7 @@
"suspend": "挂起", "suspend": "挂起",
"suspendTip": "suspend 功能需要 root 权限及 systemd 支持。", "suspendTip": "suspend 功能需要 root 权限及 systemd 支持。",
"switchTo": "切换到 {val}", "switchTo": "切换到 {val}",
"sync": "同步",
"syncTip": "可能需要重新启动,某些更改才能生效。", "syncTip": "可能需要重新启动,某些更改才能生效。",
"system": "系统", "system": "系统",
"tag": "标签", "tag": "标签",

View File

@@ -2,6 +2,7 @@
"@@locale": "zh_TW", "@@locale": "zh_TW",
"about": "關於", "about": "關於",
"aboutThanks": "感謝以下參與的各位。", "aboutThanks": "感謝以下參與的各位。",
"acceptBeta": "接受測試版更新推送",
"add": "新增", "add": "新增",
"addAServer": "新增服務器", "addAServer": "新增服務器",
"addPrivateKey": "新增一個私鑰", "addPrivateKey": "新增一個私鑰",
@@ -25,6 +26,7 @@
"backupTip": "導出的數據僅進行了簡單加密,請妥善保管。", "backupTip": "導出的數據僅進行了簡單加密,請妥善保管。",
"backupVersionNotMatch": "備份版本不匹配,無法還原", "backupVersionNotMatch": "備份版本不匹配,無法還原",
"battery": "電池", "battery": "電池",
"beforeConnect": "ServerBox將在連接後在`~/.config/server_box`寫入腳本並執行,更多的技術細節請訪問[Github]({url})。",
"bgRun": "背景運行", "bgRun": "背景運行",
"bgRunTip": "此開關只代表程式會嘗試在背景執行,具體能否背景運行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池優化”MIUI 請修改省電策略為“無限制”。", "bgRunTip": "此開關只代表程式會嘗試在背景執行,具體能否背景運行取決於是否開啟了權限。 原生 Android 請關閉本 App 的“電池優化”MIUI 請修改省電策略為“無限制”。",
"bioAuth": "生物認證", "bioAuth": "生物認證",
@@ -44,7 +46,6 @@
"cnKeyboardCompTip": "如果終端彈出安全鍵盤,您可以啟用它。", "cnKeyboardCompTip": "如果終端彈出安全鍵盤,您可以啟用它。",
"collapseUI": "折疊", "collapseUI": "折疊",
"collapseUITip": "是否預設折疊UI中存在的長列表", "collapseUITip": "是否預設折疊UI中存在的長列表",
"collectUsage": "搜集使用信息(與隱私無關)",
"conn": "連接", "conn": "連接",
"connected": "已連接", "connected": "已連接",
"container": "容器", "container": "容器",
@@ -100,6 +101,7 @@
"export": "導出", "export": "導出",
"extraArgs": "額外參數", "extraArgs": "額外參數",
"failed": "失敗", "failed": "失敗",
"fdroidReleaseTip": "如果你是從 Fdroid 下載的本應用,推薦關閉此選項",
"feedback": "反饋", "feedback": "反饋",
"feedbackOnGithub": "如果你有任何問題請在GitHub反饋", "feedbackOnGithub": "如果你有任何問題請在GitHub反饋",
"fieldMustNotEmpty": "這些輸入框不能為空。", "fieldMustNotEmpty": "這些輸入框不能為空。",
@@ -110,6 +112,7 @@
"followSystem": "跟隨系統", "followSystem": "跟隨系統",
"font": "字體", "font": "字體",
"fontSize": "字體大小", "fontSize": "字體大小",
"forExample": "例如",
"force": "強制", "force": "強制",
"foundNUpdate": "找到 {count} 個更新", "foundNUpdate": "找到 {count} 個更新",
"fullScreen": "全屏模式", "fullScreen": "全屏模式",
@@ -133,6 +136,7 @@
"imagesList": "鏡像列表", "imagesList": "鏡像列表",
"import": "導入", "import": "導入",
"inAppUpdate": "在App內更新否則使用瀏覽器下載。", "inAppUpdate": "在App內更新否則使用瀏覽器下載。",
"init": "初始化",
"inner": "內置", "inner": "內置",
"inputDomainHere": "在這裡輸入域名", "inputDomainHere": "在這裡輸入域名",
"install": "安裝", "install": "安裝",
@@ -229,6 +233,7 @@
"rememberChoice": "記住選擇", "rememberChoice": "記住選擇",
"rememberPwdInMem": "在記憶體中記住密碼", "rememberPwdInMem": "在記憶體中記住密碼",
"rememberPwdInMemTip": "用於容器、暫停等", "rememberPwdInMemTip": "用於容器、暫停等",
"rememberWindowSize": "記住窗口大小",
"remotePath": "遠端路徑", "remotePath": "遠端路徑",
"rename": "重命名", "rename": "重命名",
"reportBugsOnGithubIssue": "請到 {url} 提交問題", "reportBugsOnGithubIssue": "請到 {url} 提交問題",
@@ -280,6 +285,7 @@
"suspend": "挂起", "suspend": "挂起",
"suspendTip": "suspend 功能需要 root 權限及 systemd 支持。", "suspendTip": "suspend 功能需要 root 權限及 systemd 支持。",
"switchTo": "切換到 {val}", "switchTo": "切換到 {val}",
"sync": "同步",
"syncTip": "可能需要重新啟動,某些更改才能生效。", "syncTip": "可能需要重新啟動,某些更改才能生效。",
"system": "系統", "system": "系統",
"tag": "标签", "tag": "标签",

View File

@@ -10,21 +10,22 @@ 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:shared_preferences/shared_preferences.dart';
import 'package:toolbox/app.dart'; import 'package:server_box/app.dart';
import 'package:toolbox/core/utils/sync/icloud.dart'; import 'package:server_box/core/utils/sync/icloud.dart';
import 'package:toolbox/core/utils/sync/webdav.dart'; import 'package:server_box/core/utils/sync/webdav.dart';
import 'package:toolbox/data/model/app/menu/server_func.dart'; import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:toolbox/data/model/app/net_view.dart'; import 'package:server_box/data/model/app/net_view.dart';
import 'package:toolbox/data/model/app/server_detail_card.dart'; import 'package:server_box/data/model/app/server_detail_card.dart';
import 'package:toolbox/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:toolbox/data/model/ssh/virtual_key.dart'; import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:toolbox/data/res/build_data.dart'; import 'package:server_box/data/model/ssh/virtual_key.dart';
import 'package:toolbox/data/res/provider.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:toolbox/data/res/url.dart'; import 'package:server_box/data/res/provider.dart';
import 'package:server_box/data/res/store.dart';
Future<void> main() async { Future<void> main() async {
_runInZone(() async { _runInZone(() async {
@@ -54,7 +55,6 @@ void _runInZone(void Function() body) {
runZonedGuarded( runZonedGuarded(
body, body,
(obj, trace) { (obj, trace) {
Analysis.recordException(trace);
Loggers.root.warning(obj, null, trace); Loggers.root.warning(obj, null, trace);
}, },
zoneSpecification: zoneSpec, zoneSpecification: zoneSpec,
@@ -64,11 +64,17 @@ void _runInZone(void Function() body) {
Future<void> _initApp() async { Future<void> _initApp() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Paths.init(BuildData.name, bakName: 'srvbox'); await Paths.init(BuildData.name, bakName: Miscs.bakFileName);
await _initData(); await _initData();
_setupDebug(); _setupDebug();
SystemUIs.initDesktopWindow(Stores.setting.hideTitleBar.fetch()); final windowSize = Stores.setting.windowSize;
final hideTitleBar = Stores.setting.hideTitleBar.fetch();
SystemUIs.initDesktopWindow(
hideTitleBar: hideTitleBar,
size: windowSize.fetch().toSize(),
listener: WindowSizeListener(windowSize),
);
FontUtils.loadFrom(Stores.setting.fontPath.fetch()); FontUtils.loadFrom(Stores.setting.fontPath.fetch());
_doPlatformRelated(); _doPlatformRelated();
@@ -86,6 +92,7 @@ Future<void> _initData() async {
Hive.registerAdapter(NetViewTypeAdapter()); // 5 Hive.registerAdapter(NetViewTypeAdapter()); // 5
Hive.registerAdapter(ServerFuncBtnAdapter()); // 6 Hive.registerAdapter(ServerFuncBtnAdapter()); // 6
Hive.registerAdapter(ServerCustomAdapter()); // 7 Hive.registerAdapter(ServerCustomAdapter()); // 7
Hive.registerAdapter(WakeOnLanCfgAdapter()); // 8
await Stores.setting.init(); await Stores.setting.init();
await Stores.server.init(); await Stores.server.init();
@@ -97,6 +104,8 @@ Future<void> _initData() async {
Pros.snippet.load(); Pros.snippet.load();
Pros.key.load(); Pros.key.load();
await Pros.app.init(); await Pros.app.init();
if (Stores.setting.betaTest.fetch()) AppUpdate.chan = AppUpdateChan.beta;
} }
void _setupDebug() { void _setupDebug() {
@@ -107,10 +116,6 @@ void _setupDebug() {
if (record.error != null) print(record.error); if (record.error != null) print(record.error);
if (record.stackTrace != null) print(record.stackTrace); if (record.stackTrace != null) print(record.stackTrace);
}); });
if (Stores.setting.collectUsage.fetch()) {
Analysis.init(Urls.analysis, '0772e65c696709f879d87db77ae1a811259e3eb9');
}
} }
void _doPlatformRelated() async { void _doPlatformRelated() async {

View File

@@ -5,13 +5,14 @@ import 'package:computer/computer.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:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:toolbox/core/utils/sync/icloud.dart'; import 'package:server_box/core/utils/sync/icloud.dart';
import 'package:toolbox/core/utils/sync/webdav.dart'; import 'package:server_box/core/utils/sync/webdav.dart';
import 'package:toolbox/data/model/app/backup.dart'; import 'package:server_box/data/model/app/backup.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:toolbox/data/res/store.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:toolbox/data/res/url.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/res/url.dart';
class BackupPage extends StatelessWidget { class BackupPage extends StatelessWidget {
BackupPage({super.key}); BackupPage({super.key});
@@ -65,18 +66,7 @@ class BackupPage extends StatelessWidget {
trailing: const Icon(Icons.save), trailing: const Icon(Icons.save),
onTap: () async { onTap: () async {
final path = await Backup.backup(); final path = await Backup.backup();
debugPrint("Backup path: $path"); await Pfs.share(path: path);
/// Issue #188
switch (Pfs.type) {
case Pfs.windows:
final backslashPath = path.replaceAll('/', '\\');
await Process.run('explorer', ['/select,$backslashPath']);
case Pfs.linux:
await Process.run('xdg-open', [path]);
default:
await Pfs.sharePath(path);
}
}, },
), ),
ListTile( ListTile(
@@ -259,9 +249,9 @@ class BackupPage extends StatelessWidget {
), ),
], ],
); );
} catch (e, trace) { } catch (e, s) {
Loggers.app.warning('Import backup failed', e, trace); Loggers.app.warning('Import backup failed', e, s);
context.showSnackBar(e.toString()); context.showErrDialog(e: e, s: s, operation: l10n.restore);
} }
} }
@@ -269,89 +259,52 @@ class BackupPage extends StatelessWidget {
webdavLoading.value = true; webdavLoading.value = true;
try { try {
final files = await Webdav.list(); final files = await Webdav.list();
if (files.isEmpty) { if (files.isEmpty) return context.showSnackBar(l10n.dirEmpty);
context.showSnackBar(l10n.dirEmpty);
webdavLoading.value = false;
return;
}
final fileName = await context.showRoundDialog<String>( final fileName = await context.showPickSingleDialog(
title: l10n.restore, title: l10n.restore,
child: SizedBox( items: files,
width: 300,
height: 300,
child: ListView.builder(
itemCount: files.length,
itemBuilder: (_, index) {
final file = files[index];
return ListTile(
title: Text(file),
onTap: () => context.pop(file),
);
},
),
),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(l10n.cancel),
),
],
); );
if (fileName == null) { if (fileName == null) return;
webdavLoading.value = false;
return;
}
final result = await Webdav.download(relativePath: fileName); final result = await Webdav.download(relativePath: fileName);
if (result != null) { if (result != null) {
Loggers.app.warning('Download webdav backup failed: $result'); throw result;
webdavLoading.value = false;
return;
} }
final dlFile = await File('${Paths.doc}/$fileName').readAsString(); final dlFile = await File('${Paths.doc}/$fileName').readAsString();
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile); final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
await dlBak.restore(force: true); await dlBak.restore(force: true);
webdavLoading.value = false; } catch (e, s) {
} catch (e) { context.showErrDialog(e: e, s: s, operation: l10n.restore);
context.showSnackBar(e.toString()); Loggers.app.warning('Download webdav backup failed', e, s);
rethrow;
} finally { } finally {
webdavLoading.value = false; webdavLoading.value = false;
} }
} }
Future<void> _onTapWebdavUp(BuildContext context) async { Future<void> _onTapWebdavUp(BuildContext context) async {
webdavLoading.value = true;
final date = DateTime.now().ymdhms(ymdSep: "-", hmsSep: "-", sep: "-");
final bakName = '$date-${Miscs.bakFileName}';
try { try {
webdavLoading.value = true;
final bakName =
'${DateTime.now().ymdhms(ymdSep: "-", hmsSep: "-", sep: "-")}-${Paths.bakName}';
await Backup.backup(bakName); await Backup.backup(bakName);
final uploadResult = await Webdav.upload(relativePath: bakName); final uploadResult = await Webdav.upload(relativePath: bakName);
if (uploadResult != null) { if (uploadResult != null) {
Loggers.app.warning('Upload webdav backup failed: $uploadResult'); throw uploadResult;
context.showSnackBar(uploadResult.toString());
} else {
Loggers.app.info('Upload webdav backup success');
} }
} catch (e) { Loggers.app.info('Upload webdav backup success');
context.showSnackBar(e.toString()); } catch (e, s) {
rethrow; context.showErrDialog(e: e, s: s, operation: l10n.upload);
Loggers.app.warning('Upload webdav backup failed', e, s);
} finally { } finally {
webdavLoading.value = false; webdavLoading.value = false;
} }
} }
Future<void> _onTapWebdavSetting(BuildContext context) async { Future<void> _onTapWebdavSetting(BuildContext context) async {
final urlCtrl = TextEditingController( final url = TextEditingController(text: Stores.setting.webdavUrl.fetch());
text: Stores.setting.webdavUrl.fetch(), final user = TextEditingController(text: Stores.setting.webdavUser.fetch());
); final pwd = TextEditingController(text: Stores.setting.webdavPwd.fetch());
final userCtrl = TextEditingController(
text: Stores.setting.webdavUser.fetch(),
);
final pwdCtrl = TextEditingController(
text: Stores.setting.webdavPwd.fetch(),
);
final result = await context.showRoundDialog<bool>( final result = await context.showRoundDialog<bool>(
title: 'WebDAV', title: 'WebDAV',
child: Column( child: Column(
@@ -360,15 +313,15 @@ class BackupPage extends StatelessWidget {
Input( Input(
label: 'URL', label: 'URL',
hint: 'https://example.com/webdav/', hint: 'https://example.com/webdav/',
controller: urlCtrl, controller: url,
), ),
Input( Input(
label: l10n.user, label: l10n.user,
controller: userCtrl, controller: user,
), ),
Input( Input(
label: l10n.pwd, label: l10n.pwd,
controller: pwdCtrl, controller: pwd,
), ),
], ],
), ),
@@ -382,15 +335,13 @@ class BackupPage extends StatelessWidget {
], ],
); );
if (result == true) { if (result == true) {
final result = final result = await Webdav.test(url.text, user.text, pwd.text);
await Webdav.test(urlCtrl.text, userCtrl.text, pwdCtrl.text); if (result != null) {
if (result == null) {
context.showSnackBar(l10n.success);
} else {
context.showSnackBar(result); context.showSnackBar(result);
return; return;
} }
Webdav.changeClient(urlCtrl.text, userCtrl.text, pwdCtrl.text); context.showSnackBar(l10n.success);
Webdav.changeClient(url.text, user.text, pwd.text);
} }
} }
@@ -430,9 +381,9 @@ class BackupPage extends StatelessWidget {
), ),
], ],
); );
} catch (e, trace) { } catch (e, s) {
Loggers.app.warning('Import backup failed', e, trace); Loggers.app.warning('Import backup failed', e, s);
context.showSnackBar(e.toString()); context.showErrDialog(e: e, s: s, operation: l10n.restore);
} }
} }
@@ -467,11 +418,9 @@ class BackupPage extends StatelessWidget {
); );
context.showSnackBar(l10n.success); context.showSnackBar(l10n.success);
} }
} catch (e) { } catch (e, s) {
context.showRoundDialog( context.showErrDialog(e: e, s: s, operation: l10n.import);
title: l10n.error, Loggers.app.warning('Import servers failed', e, s);
child: Text(e.toString()),
);
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More