Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3b48fc01c | ||
|
|
8be94aa09c | ||
|
|
5db1253ab8 | ||
|
|
ceedd86310 | ||
|
|
6a0254623f | ||
|
|
1c6ec56032 | ||
|
|
287869ed45 | ||
|
|
e4dbc3ba12 | ||
|
|
426e5689f8 | ||
|
|
afda5fd4a4 | ||
|
|
0a21b2820c | ||
|
|
87b3b76b0b | ||
|
|
41ec46f1d3 | ||
|
|
7a359588db | ||
|
|
255abe8b11 | ||
|
|
b0936c5e6e | ||
|
|
2907ac74d4 | ||
|
|
ea678f37b0 | ||
|
|
076082c945 | ||
|
|
5ee98f90e8 | ||
|
|
c988dd88d7 | ||
|
|
f7d6c461dc | ||
|
|
14771ae946 | ||
|
|
7e9086b20e | ||
|
|
4b3c4870ba | ||
|
|
43cebd0c04 | ||
|
|
5ce13109b0 | ||
|
|
6e428c91d1 | ||
|
|
4430045550 | ||
|
|
282cb06091 | ||
|
|
772c2743b5 | ||
|
|
90199b89a5 | ||
|
|
e1d2e3f3e5 | ||
|
|
3f9fe1f2c6 | ||
|
|
c79bbc5756 | ||
|
|
63e1bec2b9 | ||
|
|
d26b7c6f75 | ||
|
|
da8dc4fa54 | ||
|
|
413b81c47f | ||
|
|
9ef419e3df | ||
|
|
39893912d9 | ||
|
|
49456ca6c3 | ||
|
|
6b9b8f0dbb | ||
|
|
5eb48b2717 | ||
|
|
5339cfca70 | ||
|
|
1462b2d0b8 | ||
|
|
3798a23183 | ||
|
|
ddaf916170 | ||
|
|
d6e37b058f | ||
|
|
2e9ad7d7cb | ||
|
|
190da74f66 | ||
|
|
f1315dda7f | ||
|
|
43e6105eb3 | ||
|
|
d785209eb6 | ||
|
|
da8b6a9010 | ||
|
|
1fd68722da | ||
|
|
c9a2c1d0e4 | ||
|
|
161f536a62 | ||
|
|
1a32e9944e | ||
|
|
6deb753198 | ||
|
|
4e33a98631 | ||
|
|
dcb9464d8f | ||
|
|
b94936b29f | ||
|
|
108d0a5a5b | ||
|
|
4814a2de28 | ||
|
|
5d8eeff502 | ||
|
|
0bc4087266 | ||
|
|
921209b901 | ||
|
|
fa9d754470 | ||
|
|
1f50a1f0f4 | ||
|
|
80e84c0421 | ||
|
|
5059872c3f | ||
|
|
8add244776 | ||
|
|
04e23fd7e4 | ||
|
|
336b11b808 | ||
|
|
8d9dba361c | ||
|
|
64ce3638cb | ||
|
|
f3ceb73f0e | ||
|
|
131dbe934c | ||
|
|
58288e89bd | ||
|
|
22c43c7124 | ||
|
|
2bf0b25ee5 | ||
|
|
3bc03c1364 | ||
|
|
490d71f8c9 | ||
|
|
edceb5900e | ||
|
|
e5ef28415b | ||
|
|
46b98df153 | ||
|
|
9f34021c90 | ||
|
|
8121eef839 | ||
|
|
da48d1f66c | ||
|
|
b167287c5b | ||
|
|
41f9da6bf8 | ||
|
|
e7c7fc8186 | ||
|
|
b950dd2d68 | ||
|
|
6d34de14d3 | ||
|
|
a5a84c0cdd | ||
|
|
701b1b811f | ||
|
|
97267cdfbf | ||
|
|
40ce37d230 | ||
|
|
8a9ade355c | ||
|
|
9bffec64b5 | ||
|
|
a03ee2ae0e | ||
|
|
ee889235fe | ||
|
|
94d6d80497 | ||
|
|
413c45a559 | ||
|
|
6dc5536c48 | ||
|
|
76c4bf56fa | ||
|
|
a0c6642230 | ||
|
|
4198d7bd13 | ||
|
|
b06fddec07 | ||
|
|
d1f14bee59 | ||
|
|
8953f63197 | ||
|
|
193d80d826 | ||
|
|
9e308792aa | ||
|
|
fbabd8c351 | ||
|
|
1a3cb09ca2 |
14
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: lollipopkit
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
custom: ['https://afdian.com/a/lollipopkit'] # Replace with up to 4 custom sponsorship URLs
|
||||
71
.github/workflows/release.yml
vendored
@@ -11,43 +11,59 @@ permissions:
|
||||
jobs:
|
||||
releaseAL:
|
||||
name: Release android and linux
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Flutter
|
||||
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
|
||||
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
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm64.apk
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm.apk
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_amd64.apk
|
||||
${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.AppImage
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
releaseWin:
|
||||
name: Release windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
- name: Build
|
||||
run: dart run fl_build -p android,linux
|
||||
run: dart run fl_build -p windows
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm64.apk
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm.apk
|
||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_amd64.apk
|
||||
${{ env.APP_NAME }}_amd64.AppImage
|
||||
${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_windows_amd64.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# releaseWin:
|
||||
# name: Release windows
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Install Flutter
|
||||
# uses: subosito/flutter-action@v2
|
||||
# - name: Build
|
||||
# run: dart run fl_build -p windows
|
||||
# - name: Create Release
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# with:
|
||||
# files: |
|
||||
# ${{ env.APP_NAME }}_amd64_windows.zip
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# releaseApple:
|
||||
# name: Release ios and macos
|
||||
# runs-on: macos-latest
|
||||
@@ -56,10 +72,13 @@ jobs:
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Install Flutter
|
||||
# uses: subosito/flutter-action@v2
|
||||
# with:
|
||||
# channel: 'stable'
|
||||
# flutter-version: '3.22.2'
|
||||
# - name: Build
|
||||
# run: dart run fl_build -p ios,mac
|
||||
# - name: Create Release
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# uses: softprops/action-gh-release@v2
|
||||
# with:
|
||||
# files: |
|
||||
# ${{ env.APP_NAME }}_universal_macos.zip
|
||||
|
||||
3
.gitignore
vendored
@@ -57,9 +57,10 @@ test.dart
|
||||
|
||||
# Linux release
|
||||
linux.AppDir
|
||||
ServerBox-x86_64.AppImage
|
||||
**/*.AppImage
|
||||
|
||||
untranlated.json
|
||||
|
||||
.vscode/settings.json
|
||||
more_build_data.json
|
||||
trans.txt
|
||||
|
||||
30
.metadata
@@ -4,7 +4,7 @@
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "bae5e49bc2a867403c43b2aae2de8f8c33b037e4"
|
||||
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
@@ -13,26 +13,26 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: android
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: ios
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: linux
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: macos
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: web
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: windows
|
||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
4
.vscode/launch.json
vendored
@@ -8,6 +8,10 @@
|
||||
"name": "debug",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"env": {
|
||||
// Comment this line to use the default display
|
||||
"DISPLAY": ":1"
|
||||
}
|
||||
// "args": [
|
||||
// "-v"
|
||||
// ]
|
||||
|
||||
69
README.md
@@ -4,7 +4,6 @@ English | [简体中文](README_zh.md)
|
||||
|
||||
<p align="center">
|
||||
<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">
|
||||
</p>
|
||||
|
||||
@@ -14,45 +13,38 @@ 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>.
|
||||
</p>
|
||||
|
||||
## ⬇️ 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
|
||||
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): Basically tested, with debug certificate
|
||||
|
||||
## 📥 Install
|
||||
|
||||
Platform | From
|
||||
--- | ---
|
||||
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
|
||||
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||
|
||||
**Please only download pkgs from the source that you trust!**
|
||||
- `AppStore` & `CDN` packages are built by myself
|
||||
- Github releases are built by Github Actions
|
||||
- Other sources are built by themselves
|
||||
|
||||
|
||||
## 🔖 Feature
|
||||
- Status chart, `SSH` Terminal, `SFTP`, `Docker & Pkg & Process`, Code editor...
|
||||
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`...
|
||||
- Platform specific: `Bio auth`、`Msg push`、`Home widget`、`watchOS App`...
|
||||
- Localization
|
||||
- English, 简体中文
|
||||
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||
- Deutsch (@its-tom) / 繁體中文 (@kalashnikov) / Indonesian (@azkadev) / Français (@FrancXPT) / Dutch (@QazCetelic)
|
||||
- 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)
|
||||
|
||||
|
||||
## 🏙️ ScreenShots
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="277px" src="imgs/server.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/detail.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/sftp.png">
|
||||
</td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/1.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/2.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/3.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="277px" src="imgs/editor.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/ssh.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/docker.png">
|
||||
</td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/4.png"> </td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/5.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/6.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -66,25 +58,24 @@ Before you open an issue, please read the following:
|
||||
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.
|
||||
|
||||
After you read the above, you can:
|
||||
- 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).
|
||||
After you read the above, you can open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
|
||||
|
||||
|
||||
## 🧱 Contribution
|
||||
- Any positive contribution is welcome.
|
||||
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.
|
||||
Any positive contribution is welcome.
|
||||
|
||||
### Development
|
||||
1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment.
|
||||
2. Clone this repo, run `flutter run` to start the app.
|
||||
3. Run `dart run fl_build -p PLATFORM` to build the app.
|
||||
|
||||
## 👏🏼 Contributors
|
||||
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
|
||||
</a>
|
||||
### Translation
|
||||
- [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog.
|
||||
- We need your help! Just feel free to open a PR.
|
||||
|
||||
|
||||
## 💡 My other apps
|
||||
- [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.
|
||||
|
||||
|
||||
|
||||
62
README_zh.md
@@ -4,7 +4,6 @@
|
||||
|
||||
<p align="center">
|
||||
<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">
|
||||
</p>
|
||||
|
||||
@@ -14,14 +13,22 @@
|
||||
特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>。
|
||||
</p>
|
||||
|
||||
## 📥 安装
|
||||
|
||||
## ⬇️ Download
|
||||
[iOS](https://apps.apple.com/app/id1586449703) / [Android](https://cdn.lolli.tech/serverbox/latest.apk) / [macOS](https://apps.apple.com/app/id1586449703): 经过测试,使用自签名证书
|
||||
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): 经过不完全测试,使用调试证书
|
||||
平台 | 下载
|
||||
--- | ---
|
||||
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
|
||||
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||
|
||||
**请不要从不受信任的来源下载!**
|
||||
- `AppStore` & `CDN` 的包由我构建
|
||||
- Github 的包由 Github Actions 构建
|
||||
- 其他来源由其所有者构建
|
||||
|
||||
|
||||
## 🔖 特点
|
||||
- 状态图表, `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器, 代码编辑器...
|
||||
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程` 管理...
|
||||
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
||||
- 本地化
|
||||
- English, 简体中文
|
||||
@@ -32,28 +39,14 @@
|
||||
## 🏙️ 截屏
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="277px" src="imgs/server.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/detail.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/sftp.png">
|
||||
</td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/1.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/2.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/3.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="277px" src="imgs/editor.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/ssh.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="277px" src="imgs/docker.png">
|
||||
</td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/4.png"> </td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/5.png"></td>
|
||||
<td><img width="277px" src="https://cdn.lolli.tech/serverbox/screenshot/6.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -69,25 +62,22 @@
|
||||
2. 反馈问题前请检查是否是 serverbox 的问题。
|
||||
3. 欢迎所有有效、正面的反馈,主观(比如你觉得其他UI更好看)的反馈不一定会接受
|
||||
|
||||
确认了解上述内容后:
|
||||
- 如果你有**任何问题或者功能请求**,请在 [讨论](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose) 中交流。
|
||||
- 如果 ServerBox app 有**任何 bug**,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
|
||||
确认了解上述内容后,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
|
||||
|
||||
|
||||
## 🧱 贡献
|
||||
- 任何正面的贡献都欢迎。
|
||||
- [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
||||
任何正面的贡献都欢迎。
|
||||
|
||||
### 开发
|
||||
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
|
||||
2. 克隆这个仓库, 运行 `flutter run` 启动应用
|
||||
3. 运行 `dart run fl_build -p PLATFORM` 构建应用
|
||||
|
||||
## 👏🏼 贡献者
|
||||
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
|
||||
</a>
|
||||
|
||||
### 翻译
|
||||
[指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
||||
|
||||
## 💡 我的其它 Apps
|
||||
- [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.
|
||||
|
||||
|
||||
|
||||
@@ -100,3 +100,14 @@ flutter {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
android/app/proguard-rules.pro
vendored
@@ -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.** { *; }
|
||||
|
||||
@@ -3,3 +3,4 @@ distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
|
||||
|
||||
6
fastlane/metadata/android/en-US/full_description.txt
Normal 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, 日本語
|
||||
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
A server status & toolbox app using Flutter
|
||||
1
fastlane/metadata/android/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
ServerBox
|
||||
7
fastlane/metadata/android/zh-CN/full_description.txt
Normal 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
|
||||
BIN
fastlane/metadata/android/zh-CN/images/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
1
fastlane/metadata/android/zh-CN/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
使用 Flutter 开发的服务器状态和工具箱应用
|
||||
1
fastlane/metadata/android/zh-CN/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
ServerBox
|
||||
|
Before Width: | Height: | Size: 112 KiB |
@@ -1,5 +1,5 @@
|
||||
PODS:
|
||||
- countly_flutter (24.4.0):
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- Flutter
|
||||
@@ -10,7 +10,7 @@ PODS:
|
||||
- Flutter
|
||||
- icloud_storage (0.0.1):
|
||||
- Flutter
|
||||
- local_auth_ios (0.0.1):
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
@@ -21,8 +21,6 @@ PODS:
|
||||
- Flutter
|
||||
- plain_notification_token (0.0.1):
|
||||
- Flutter
|
||||
- r_upgrade (0.0.1):
|
||||
- Flutter
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -36,18 +34,17 @@ PODS:
|
||||
- Flutter
|
||||
|
||||
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`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/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`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/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`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
@@ -55,8 +52,8 @@ DEPENDENCIES:
|
||||
- watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
countly_flutter:
|
||||
:path: ".symlinks/plugins/countly_flutter/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
@@ -67,8 +64,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
icloud_storage:
|
||||
:path: ".symlinks/plugins/icloud_storage/ios"
|
||||
local_auth_ios:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
@@ -77,8 +74,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
plain_notification_token:
|
||||
:path: ".symlinks/plugins/plain_notification_token/ios"
|
||||
r_upgrade:
|
||||
:path: ".symlinks/plugins/r_upgrade/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@@ -91,21 +86,20 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/watch_connectivity/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
countly_flutter: 5d2febe00242796cf569662e5d47da241f31b115
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a
|
||||
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
||||
local_auth_darwin: 4d56c90c2683319835a61274b57620df9c4520ab
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
|
||||
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -700,7 +700,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -826,7 +826,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -836,7 +836,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -854,7 +854,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -864,7 +864,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -885,7 +885,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -898,7 +898,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
@@ -924,7 +924,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -937,7 +937,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -960,7 +960,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -973,7 +973,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -996,7 +996,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1008,7 +1008,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
@@ -1037,7 +1037,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1049,7 +1049,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
@@ -1075,7 +1075,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 918;
|
||||
CURRENT_PROJECT_VERSION = 1034;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1087,7 +1087,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.918;
|
||||
MARKETING_VERSION = 1.0.1034;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
|
||||
92
lib/app.dart
@@ -1,12 +1,17 @@
|
||||
import 'package:dynamic_color/dynamic_color.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_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/data/res/build_data.dart';
|
||||
import 'package:toolbox/data/res/rebuild.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:toolbox/view/page/home/home.dart';
|
||||
import 'package:locale_names/locale_names.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/res/rebuild.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 {
|
||||
const MyApp({super.key});
|
||||
@@ -15,11 +20,22 @@ class MyApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
_setup(context);
|
||||
return ListenableBuilder(
|
||||
listenable: RebuildNodes.app,
|
||||
builder: (_, __) {
|
||||
listenable: RNodes.app,
|
||||
builder: (context, _) {
|
||||
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
||||
UIs.colorSeed = Color(Stores.setting.primaryColor.fetch());
|
||||
return _buildApp(context);
|
||||
return _buildApp(
|
||||
context,
|
||||
light: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
),
|
||||
dark: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
),
|
||||
);
|
||||
}
|
||||
return DynamicColorBuilder(
|
||||
builder: (light, dark) {
|
||||
@@ -32,10 +48,10 @@ class MyApp extends StatelessWidget {
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: dark,
|
||||
);
|
||||
if (context.isDark && light != null) {
|
||||
UIs.primaryColor = light.primary;
|
||||
} else if (!context.isDark && dark != null) {
|
||||
if (context.isDark && dark != null) {
|
||||
UIs.primaryColor = dark.primary;
|
||||
} else if (!context.isDark && light != null) {
|
||||
UIs.primaryColor = light.primary;
|
||||
}
|
||||
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
||||
},
|
||||
@@ -44,7 +60,8 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApp(BuildContext ctx, {ThemeData? light, ThemeData? dark}) {
|
||||
Widget _buildApp(BuildContext ctx,
|
||||
{required ThemeData light, required ThemeData dark}) {
|
||||
final tMode = Stores.setting.themeMode.fetch();
|
||||
// Issue #57
|
||||
final themeMode = switch (tMode) {
|
||||
@@ -54,16 +71,6 @@ class MyApp extends StatelessWidget {
|
||||
};
|
||||
final locale = Stores.setting.locale.fetch().toLocale;
|
||||
|
||||
light ??= ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
);
|
||||
dark ??= ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: UIs.colorSeed,
|
||||
);
|
||||
|
||||
return MaterialApp(
|
||||
locale: locale,
|
||||
localizationsDelegates: const [
|
||||
@@ -71,35 +78,30 @@ class MyApp extends StatelessWidget {
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
localeListResolutionCallback: LocaleUtil.resolve,
|
||||
navigatorObservers: [AppRouteObserver.instance],
|
||||
title: BuildData.name,
|
||||
themeMode: themeMode,
|
||||
theme: light,
|
||||
darkTheme: tMode < 3 ? dark : _getAmoledTheme(dark),
|
||||
home: _buildAppContent(ctx),
|
||||
);
|
||||
}
|
||||
theme: light.fixWindowsFont,
|
||||
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
context.setLibL10n();
|
||||
final appL10n = AppLocalizations.of(context);
|
||||
if (appL10n != null) l10n = appL10n;
|
||||
|
||||
Widget _buildAppContent(BuildContext ctx) {
|
||||
//if (Pros.app.isWearOS) return const WearHome();
|
||||
return const HomePage();
|
||||
final intros = _IntroPage.builders;
|
||||
if (intros.isNotEmpty) {
|
||||
return _IntroPage(intros);
|
||||
}
|
||||
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _setup(BuildContext context) async {
|
||||
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),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:toolbox/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
|
||||
abstract final class BgRunMC {
|
||||
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:toolbox/data/res/misc.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
abstract final class HomeWidgetMC {
|
||||
static const _channel = MethodChannel('${Miscs.pkgName}/home_widget');
|
||||
|
||||
@@ -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 {
|
||||
static const versionStr = 'v1.0.${BuildData.build}';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:server_box/view/widget/unix_perm.dart';
|
||||
|
||||
extension SftpFileX on SftpFileMode {
|
||||
String get str {
|
||||
@@ -8,6 +9,26 @@ extension SftpFileX on SftpFileMode {
|
||||
|
||||
return '$user$group$other';
|
||||
}
|
||||
|
||||
UnixPerm toUnixPerm() {
|
||||
return UnixPerm(
|
||||
user: RWX(
|
||||
r: userRead,
|
||||
w: userWrite,
|
||||
x: userExecute,
|
||||
),
|
||||
group: RWX(
|
||||
r: groupRead,
|
||||
w: groupWrite,
|
||||
x: groupExecute,
|
||||
),
|
||||
other: RWX(
|
||||
r: otherRead,
|
||||
w: otherWrite,
|
||||
x: otherExecute,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _getRoleMode(bool r, bool w, bool x) {
|
||||
|
||||
@@ -79,7 +79,9 @@ extension SSHClientX on SSHClient {
|
||||
isRequestingPwd = true;
|
||||
final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1);
|
||||
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) {
|
||||
session.kill(SSHSignal.TERM);
|
||||
} else {
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/res/build_data.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:toolbox/view/page/backup.dart';
|
||||
import 'package:toolbox/view/page/container.dart';
|
||||
import 'package:toolbox/view/page/home/home.dart';
|
||||
import 'package:toolbox/view/page/iperf.dart';
|
||||
import 'package:toolbox/view/page/ping.dart';
|
||||
import 'package:toolbox/view/page/private_key/edit.dart';
|
||||
import 'package:toolbox/view/page/private_key/list.dart';
|
||||
import 'package:toolbox/view/page/pve.dart';
|
||||
import 'package:toolbox/view/page/server/detail/view.dart';
|
||||
import 'package:toolbox/view/page/setting/platform/android.dart';
|
||||
import 'package:toolbox/view/page/setting/platform/ios.dart';
|
||||
import 'package:toolbox/view/page/setting/seq/srv_func_seq.dart';
|
||||
import 'package:toolbox/view/page/snippet/result.dart';
|
||||
import 'package:toolbox/view/page/ssh/page.dart';
|
||||
import 'package:toolbox/view/page/setting/seq/virt_key.dart';
|
||||
import 'package:toolbox/view/page/storage/local.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/view/page/backup.dart';
|
||||
import 'package:server_box/view/page/container.dart';
|
||||
import 'package:server_box/view/page/home/home.dart';
|
||||
import 'package:server_box/view/page/iperf.dart';
|
||||
import 'package:server_box/view/page/ping.dart';
|
||||
import 'package:server_box/view/page/private_key/edit.dart';
|
||||
import 'package:server_box/view/page/private_key/list.dart';
|
||||
import 'package:server_box/view/page/pve.dart';
|
||||
import 'package:server_box/view/page/server/detail/view.dart';
|
||||
import 'package:server_box/view/page/setting/platform/android.dart';
|
||||
import 'package:server_box/view/page/setting/platform/ios.dart';
|
||||
import 'package:server_box/view/page/setting/seq/srv_func_seq.dart';
|
||||
import 'package:server_box/view/page/snippet/result.dart';
|
||||
import 'package:server_box/view/page/ssh/page.dart';
|
||||
import 'package:server_box/view/page/setting/seq/virt_key.dart';
|
||||
import 'package:server_box/view/page/storage/local.dart';
|
||||
|
||||
import '../data/model/server/snippet.dart';
|
||||
import '../view/page/editor.dart';
|
||||
@@ -102,12 +102,14 @@ class AppRoutes {
|
||||
Key? key,
|
||||
required ServerPrivateInfo spi,
|
||||
String? initCmd,
|
||||
Snippet? initSnippet,
|
||||
}) {
|
||||
return AppRoutes(
|
||||
SSHPage(
|
||||
key: key,
|
||||
spi: spi,
|
||||
initCmd: initCmd,
|
||||
initSnippet: initSnippet,
|
||||
),
|
||||
'ssh_term',
|
||||
);
|
||||
@@ -246,4 +248,11 @@ class AppRoutes {
|
||||
static AppRoutes pve({Key? key, required ServerPrivateInfo spi}) {
|
||||
return AppRoutes(PvePage(key: key, spi: spi), 'pve');
|
||||
}
|
||||
|
||||
static AppRoutes kvEditor({Key? key, required Map<String, String> data}) {
|
||||
return AppRoutes(
|
||||
KvEditor(key: key, args: KvEditorArgs(data: data)),
|
||||
'kv_editor',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,9 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:plain_notification_token/plain_notification_token.dart';
|
||||
|
||||
Future<String?> getToken() async {
|
||||
if (isIOS) {
|
||||
final plainNotificationToken = PlainNotificationToken();
|
||||
plainNotificationToken.requestPermission();
|
||||
|
||||
// If you want to wait until Permission dialog close,
|
||||
// you need wait changing setting registered.
|
||||
await plainNotificationToken.onIosSettingsRegistered.first;
|
||||
return await plainNotificationToken.getToken();
|
||||
}
|
||||
return null;
|
||||
if (!isIOS) return null;
|
||||
final instance = ApnsToken()..requestPermission();
|
||||
// Wait until Permission dialog closed
|
||||
await instance.onIosSettingsRegistered.first;
|
||||
return await instance.getToken();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
import '../../data/model/server/server_private_info.dart';
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
|
||||
abstract final class KeybordInteractive {
|
||||
static FutureOr<List<String>?> defaultHandle(
|
||||
|
||||
@@ -5,8 +5,9 @@ import 'package:computer/computer.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:icloud_storage/icloud_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/data/model/app/backup.dart';
|
||||
import 'package:toolbox/data/model/app/sync.dart';
|
||||
import 'package:server_box/data/model/app/backup.dart';
|
||||
import 'package:server_box/data/model/app/sync.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
|
||||
import '../../../data/model/app/error.dart';
|
||||
|
||||
@@ -198,14 +199,13 @@ abstract final class ICloud {
|
||||
}
|
||||
|
||||
static Future<void> sync() async {
|
||||
final result = await download(relativePath: Paths.bakName);
|
||||
final result = await download(relativePath: Miscs.bakFileName);
|
||||
if (result != null) {
|
||||
_logger.warning('Download backup failed: $result');
|
||||
await backup();
|
||||
return;
|
||||
}
|
||||
|
||||
final dlFile = await File(Paths.bakPath).readAsString();
|
||||
final dlFile = await File(Paths.bak).readAsString();
|
||||
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
|
||||
await dlBak.restore();
|
||||
|
||||
@@ -214,7 +214,7 @@ abstract final class ICloud {
|
||||
|
||||
static Future<void> backup() async {
|
||||
await Backup.backup();
|
||||
final uploadResult = await upload(relativePath: Paths.bakName);
|
||||
final uploadResult = await upload(relativePath: Miscs.bakFileName);
|
||||
if (uploadResult != null) {
|
||||
_logger.warning('Upload backup failed: $uploadResult');
|
||||
} else {
|
||||
|
||||
@@ -3,9 +3,10 @@ import 'dart:io';
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/data/model/app/backup.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/app/backup.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:webdav_client/webdav_client.dart';
|
||||
|
||||
abstract final class Webdav {
|
||||
@@ -96,15 +97,14 @@ abstract final class Webdav {
|
||||
}
|
||||
|
||||
static Future<void> sync() async {
|
||||
final result = await download(relativePath: Paths.bakName);
|
||||
final result = await download(relativePath: Miscs.bakFileName);
|
||||
if (result != null) {
|
||||
_logger.warning('Download failed: $result');
|
||||
await backup();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final dlFile = await File(Paths.bakPath).readAsString();
|
||||
final dlFile = await File(Paths.bak).readAsString();
|
||||
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
|
||||
await dlBak.restore();
|
||||
} catch (e) {
|
||||
@@ -117,7 +117,7 @@ abstract final class Webdav {
|
||||
/// Create a local backup and upload it to WebDAV
|
||||
static Future<void> backup() async {
|
||||
await Backup.backup();
|
||||
final uploadResult = await upload(relativePath: Paths.bakName);
|
||||
final uploadResult = await upload(relativePath: Miscs.bakFileName);
|
||||
if (uploadResult != null) {
|
||||
_logger.warning('Upload failed: $uploadResult');
|
||||
} else {
|
||||
|
||||
@@ -3,12 +3,13 @@ import 'dart:io';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/model/server/snippet.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:toolbox/data/res/rebuild.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/rebuild.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
const backupFormatVersion = 1;
|
||||
|
||||
@@ -74,7 +75,7 @@ class Backup {
|
||||
|
||||
static Future<String> backup([String? name]) async {
|
||||
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);
|
||||
return path;
|
||||
}
|
||||
@@ -169,7 +170,7 @@ class Backup {
|
||||
}
|
||||
|
||||
Pros.reload();
|
||||
RebuildNodes.app.rebuild();
|
||||
RNodes.app.notify();
|
||||
|
||||
_logger.info('Restore success');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
|
||||
enum ErrFrom {
|
||||
unknown,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
typedef GhId = String;
|
||||
|
||||
extension GhIdX on GhId {
|
||||
String get url => 'https://github.com/$this';
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
|
||||
enum ContainerMenu {
|
||||
start,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.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';
|
||||
|
||||
@@ -15,8 +15,8 @@ enum ServerFuncBtn {
|
||||
container,
|
||||
@HiveField(3)
|
||||
process,
|
||||
@HiveField(4)
|
||||
pkg,
|
||||
//@HiveField(4)
|
||||
//pkg,
|
||||
@HiveField(5)
|
||||
snippet,
|
||||
@HiveField(6)
|
||||
@@ -30,14 +30,14 @@ enum ServerFuncBtn {
|
||||
sftp,
|
||||
container,
|
||||
process,
|
||||
pkg,
|
||||
//pkg,
|
||||
snippet,
|
||||
].map((e) => e.index).toList();
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
sftp => Icons.insert_drive_file,
|
||||
snippet => Icons.code,
|
||||
pkg => Icons.system_security_update,
|
||||
//pkg => Icons.system_security_update,
|
||||
container => FontAwesome.docker_brand,
|
||||
process => Icons.list_alt_outlined,
|
||||
terminal => Icons.terminal,
|
||||
@@ -47,7 +47,7 @@ enum ServerFuncBtn {
|
||||
String get toStr => switch (this) {
|
||||
sftp => 'SFTP',
|
||||
snippet => l10n.snippet,
|
||||
pkg => l10n.pkg,
|
||||
//pkg => l10n.pkg,
|
||||
container => l10n.container,
|
||||
process => l10n.process,
|
||||
terminal => l10n.terminal,
|
||||
|
||||
@@ -21,8 +21,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
return ServerFuncBtn.container;
|
||||
case 3:
|
||||
return ServerFuncBtn.process;
|
||||
case 4:
|
||||
return ServerFuncBtn.pkg;
|
||||
case 5:
|
||||
return ServerFuncBtn.snippet;
|
||||
case 6:
|
||||
@@ -47,9 +45,6 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
case ServerFuncBtn.process:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case ServerFuncBtn.pkg:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
case ServerFuncBtn.snippet:
|
||||
writer.writeByte(5);
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/model/server/server.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
part 'net_view.g.dart';
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
enum ServerDetailCards {
|
||||
about(Icons.info),
|
||||
|
||||
@@ -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 '../server/system.dart';
|
||||
@@ -12,52 +12,32 @@ enum ShellFunc {
|
||||
suspend,
|
||||
;
|
||||
|
||||
static const _homeVar = '\$HOME';
|
||||
static const seperator = 'SrvBoxSep';
|
||||
|
||||
/// The suffix `\t` is for formatting
|
||||
static const cmdDivider = '\necho $seperator\n\t';
|
||||
static const _srvBoxDir = '.config/server_box';
|
||||
static const scriptFile = 'mobile_v${BuildData.script}.sh';
|
||||
|
||||
/// Issue #159
|
||||
///
|
||||
/// Use script commit count as version of shell script.
|
||||
///
|
||||
/// So different version of app can run at the same time.
|
||||
///
|
||||
/// **Can't** use it in SFTP, because SFTP can't recognize `$HOME`
|
||||
static String getShellPath(String home) => '$home/$_srvBoxDir/$scriptFile';
|
||||
/// srvboxm -> ServerBox Mobile
|
||||
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||
static const scriptDir = '~/.config/server_box';
|
||||
static const scriptPath = '$scriptDir/$scriptFile';
|
||||
|
||||
static const srvBoxDir = '$_homeVar/$_srvBoxDir';
|
||||
static const _installShellPath = '$_homeVar/$_srvBoxDir/$scriptFile';
|
||||
|
||||
// Issue #299, chmod ~/.config to avoid permission issue
|
||||
static const installShellCmd = """
|
||||
chmod +x ~/.config &> /dev/null
|
||||
mkdir -p $_homeVar/$_srvBoxDir
|
||||
cat > $_installShellPath
|
||||
chmod +x $_installShellPath
|
||||
static const String installShellCmd = """
|
||||
mkdir -p $scriptDir
|
||||
cat > $scriptPath
|
||||
chmod 744 $scriptPath
|
||||
""";
|
||||
|
||||
String get flag {
|
||||
switch (this) {
|
||||
case ShellFunc.status:
|
||||
return 's';
|
||||
// case ShellFunc.docker:
|
||||
// return 'd';
|
||||
case ShellFunc.process:
|
||||
return 'p';
|
||||
case ShellFunc.shutdown:
|
||||
return 'sd';
|
||||
case ShellFunc.reboot:
|
||||
return 'r';
|
||||
case ShellFunc.suspend:
|
||||
return 'sp';
|
||||
}
|
||||
}
|
||||
String get flag => switch (this) {
|
||||
ShellFunc.process => 'p',
|
||||
ShellFunc.shutdown => 'sd',
|
||||
ShellFunc.reboot => 'r',
|
||||
ShellFunc.suspend => 'sp',
|
||||
ShellFunc.status => 's',
|
||||
// ShellFunc.docker=> 'd',
|
||||
};
|
||||
|
||||
String get exec => 'sh $_installShellPath -$flag';
|
||||
String get exec => 'sh $scriptPath -$flag';
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
@@ -213,6 +193,7 @@ enum StatusCmdType {
|
||||
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||
nvidia._('nvidia-smi -q -x'),
|
||||
sensors._('sensors'),
|
||||
cpuBrand._('cat /proc/cpuinfo | grep "model name"'),
|
||||
;
|
||||
|
||||
final String cmd;
|
||||
@@ -231,6 +212,7 @@ enum BSDStatusCmdType {
|
||||
mem._('top -l 1 | grep PhysMem'),
|
||||
//temp,
|
||||
host._('hostname'),
|
||||
cpuBrand._('sysctl -n machdep.cpu.brand_string'),
|
||||
;
|
||||
|
||||
final String cmd;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/view/page/ping.dart';
|
||||
import 'package:toolbox/view/page/server/tab.dart';
|
||||
import 'package:toolbox/view/page/snippet/list.dart';
|
||||
import 'package:toolbox/view/page/ssh/tab.dart';
|
||||
import 'package:server_box/view/page/ping.dart';
|
||||
import 'package:server_box/view/page/server/tab.dart';
|
||||
import 'package:server_box/view/page/snippet/list.dart';
|
||||
import 'package:server_box/view/page/ssh/tab.dart';
|
||||
|
||||
enum AppTab {
|
||||
server,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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 {
|
||||
final String? repository = null;
|
||||
@@ -72,7 +72,7 @@ final class DockerImg implements ContainerImg {
|
||||
final String repository;
|
||||
final String size;
|
||||
@override
|
||||
final String tag;
|
||||
final String? tag;
|
||||
|
||||
DockerImg({
|
||||
required this.containers,
|
||||
@@ -95,14 +95,30 @@ final class DockerImg implements ContainerImg {
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory DockerImg.fromJson(Map<String, dynamic> json) => DockerImg(
|
||||
containers: json["Containers"],
|
||||
createdAt: json["CreatedAt"],
|
||||
id: json["ID"],
|
||||
repository: json["Repository"],
|
||||
size: json["Size"],
|
||||
tag: json["Tag"],
|
||||
);
|
||||
factory DockerImg.fromJson(Map<String, dynamic> json) {
|
||||
final containers = switch (json["Containers"]) {
|
||||
final String a => a,
|
||||
final Object? a => a.toString(),
|
||||
};
|
||||
final repo = switch (json["Repository"] ?? json["Names"]) {
|
||||
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() => {
|
||||
"Containers": containers,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/model/container/type.dart';
|
||||
import 'package:server_box/core/extension/context/locale.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? image = null;
|
||||
String? get name;
|
||||
@@ -16,7 +17,7 @@ abstract final class ContainerPs {
|
||||
String? net;
|
||||
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);
|
||||
}
|
||||
@@ -110,8 +111,6 @@ final class PodmanPs implements ContainerPs {
|
||||
}
|
||||
|
||||
final class DockerPs implements ContainerPs {
|
||||
final String? command;
|
||||
final String? createdAt;
|
||||
@override
|
||||
final String? id;
|
||||
@override
|
||||
@@ -129,8 +128,6 @@ final class DockerPs implements ContainerPs {
|
||||
String? disk;
|
||||
|
||||
DockerPs({
|
||||
this.command,
|
||||
this.createdAt,
|
||||
this.id,
|
||||
this.image,
|
||||
this.names,
|
||||
@@ -141,10 +138,13 @@ final class DockerPs implements ContainerPs {
|
||||
String? get name => names;
|
||||
|
||||
@override
|
||||
String? get cmd => command;
|
||||
String? get cmd => null;
|
||||
|
||||
@override
|
||||
bool get running => state == 'running';
|
||||
bool get running {
|
||||
if (state?.contains('Exited') == true) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void parseStats(String s) {
|
||||
@@ -155,26 +155,15 @@ final class DockerPs implements ContainerPs {
|
||||
disk = stats['BlockIO'];
|
||||
}
|
||||
|
||||
factory DockerPs.fromRawJson(String str) =>
|
||||
DockerPs.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory DockerPs.fromJson(Map<String, dynamic> json) => DockerPs(
|
||||
command: json["Command"],
|
||||
createdAt: json["CreatedAt"],
|
||||
id: json["ID"],
|
||||
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,
|
||||
};
|
||||
/// CONTAINER ID NAMES IMAGE STATUS
|
||||
/// a049d689e7a1 aria2-pro p3terx/aria2-pro Up 3 weeks
|
||||
factory DockerPs.parse(String raw) {
|
||||
final parts = raw.split(Miscs.multiBlankreg);
|
||||
return DockerPs(
|
||||
id: parts[0],
|
||||
state: parts[1],
|
||||
names: parts[2],
|
||||
image: parts[3].trim(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:toolbox/data/model/container/image.dart';
|
||||
import 'package:toolbox/data/model/container/ps.dart';
|
||||
import 'package:server_box/data/model/container/image.dart';
|
||||
import 'package:server_box/data/model/container/ps.dart';
|
||||
|
||||
enum ContainerType {
|
||||
docker,
|
||||
@@ -7,7 +7,7 @@ enum ContainerType {
|
||||
;
|
||||
|
||||
ContainerPs Function(String str) get ps => switch (this) {
|
||||
ContainerType.docker => DockerPs.fromRawJson,
|
||||
ContainerType.docker => DockerPs.parse,
|
||||
ContainerType.podman => PodmanPs.fromRawJson,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:toolbox/data/model/server/dist.dart';
|
||||
import 'package:server_box/data/model/server/dist.dart';
|
||||
|
||||
enum PkgManager {
|
||||
apt,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:toolbox/data/model/pkg/manager.dart';
|
||||
import 'package:server_box/data/model/pkg/manager.dart';
|
||||
|
||||
class UpgradePkgInfo {
|
||||
final PkgManager? _mgr;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:toolbox/data/model/server/time_seq.dart';
|
||||
import 'package:toolbox/data/res/status.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/server/time_seq.dart';
|
||||
import 'package:server_box/data/res/status.dart';
|
||||
|
||||
/// Capacity of the FIFO queue
|
||||
const _kCap = 30;
|
||||
|
||||
class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
Cpus(super.init1, super.init2);
|
||||
|
||||
final Map<String, int> brand = {};
|
||||
|
||||
@override
|
||||
void onUpdate() {
|
||||
_coresCount = now.length;
|
||||
@@ -23,10 +25,16 @@ class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
|
||||
double usedPercent({int coreIdx = 0}) {
|
||||
if (now.length != pre.length) return 0;
|
||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||
final used = idleDelta / totalDelta;
|
||||
return used.isNaN ? 0 : 100 - used * 100;
|
||||
if (now.isEmpty) return 0;
|
||||
try {
|
||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||
final used = idleDelta / totalDelta;
|
||||
return used.isNaN ? 0 : 100 - used * 100;
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Cpus.usedPercent()', e, s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int _coresCount = 0;
|
||||
@@ -175,6 +183,22 @@ class SingleCpuCore extends TimeSeqIface<SingleCpuCore> {
|
||||
}
|
||||
}
|
||||
|
||||
final class CpuBrand {
|
||||
static Map<String, int> parse(String raw) {
|
||||
final lines = raw.split('\n');
|
||||
// {brand: count}
|
||||
final brands = <String, int>{};
|
||||
for (var line in lines) {
|
||||
if (line.contains('model name')) {
|
||||
final model = line.split(':').last.trim();
|
||||
final count = brands[model] ?? 0;
|
||||
brands[model] = count + 1;
|
||||
}
|
||||
}
|
||||
return brands;
|
||||
}
|
||||
}
|
||||
|
||||
final _bsdCpuPercentReg = RegExp(r'(\d+\.\d+)%');
|
||||
|
||||
/// TODO: Change this implementation to parse cpu status on BSD system
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 {
|
||||
lxc,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 String raw;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||
import 'package:toolbox/data/model/server/battery.dart';
|
||||
import 'package:toolbox/data/model/server/conn.dart';
|
||||
import 'package:toolbox/data/model/server/cpu.dart';
|
||||
import 'package:toolbox/data/model/server/disk.dart';
|
||||
import 'package:toolbox/data/model/server/memory.dart';
|
||||
import 'package:toolbox/data/model/server/net_speed.dart';
|
||||
import 'package:toolbox/data/model/server/nvdia.dart';
|
||||
import 'package:toolbox/data/model/server/sensors.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/model/server/system.dart';
|
||||
import 'package:toolbox/data/model/server/temp.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/server/battery.dart';
|
||||
import 'package:server_box/data/model/server/conn.dart';
|
||||
import 'package:server_box/data/model/server/cpu.dart';
|
||||
import 'package:server_box/data/model/server/disk.dart';
|
||||
import 'package:server_box/data/model/server/memory.dart';
|
||||
import 'package:server_box/data/model/server/net_speed.dart';
|
||||
import 'package:server_box/data/model/server/nvdia.dart';
|
||||
import 'package:server_box/data/model/server/sensors.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/server/temp.dart';
|
||||
|
||||
import '../app/tag_pickable.dart';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:toolbox/data/model/server/custom.dart';
|
||||
import 'package:toolbox/data/model/server/server.dart';
|
||||
import 'package:toolbox/data/model/server/wol_cfg.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:server_box/data/model/server/custom.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
|
||||
import '../app/error.dart';
|
||||
|
||||
@@ -42,6 +42,10 @@ class ServerPrivateInfo {
|
||||
@HiveField(11)
|
||||
final WakeOnLanCfg? wolCfg;
|
||||
|
||||
/// It only applies to SSH terminal.
|
||||
@HiveField(12)
|
||||
final Map<String, String>? envs;
|
||||
|
||||
final String id;
|
||||
|
||||
const ServerPrivateInfo({
|
||||
@@ -57,6 +61,7 @@ class ServerPrivateInfo {
|
||||
this.jumpId,
|
||||
this.custom,
|
||||
this.wolCfg,
|
||||
this.envs,
|
||||
}) : id = '$user@$ip:$port';
|
||||
|
||||
static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
|
||||
@@ -64,7 +69,7 @@ class ServerPrivateInfo {
|
||||
final port = json["port"] as int? ?? 22;
|
||||
final user = json["user"] as String? ?? 'root';
|
||||
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 tags = (json["tags"] as List?)?.cast<String>();
|
||||
final alterUrl = json["alterUrl"] as String?;
|
||||
@@ -76,6 +81,15 @@ class ServerPrivateInfo {
|
||||
final wolCfg = json["wolCfg"] == null
|
||||
? null
|
||||
: WakeOnLanCfg.fromJson(json["wolCfg"].cast<String, dynamic>());
|
||||
final envs_ = json["envs"] as Map<String, dynamic>?;
|
||||
final envs = <String, String>{};
|
||||
if (envs_ != null) {
|
||||
envs_.forEach((key, value) {
|
||||
if (value is String) {
|
||||
envs[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ServerPrivateInfo(
|
||||
name: name,
|
||||
@@ -90,6 +104,7 @@ class ServerPrivateInfo {
|
||||
jumpId: jumpId,
|
||||
custom: custom,
|
||||
wolCfg: wolCfg,
|
||||
envs: envs.isEmpty ? null : envs,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,7 +115,7 @@ class ServerPrivateInfo {
|
||||
data["port"] = port;
|
||||
data["user"] = user;
|
||||
if (pwd != null) {
|
||||
data["authorization"] = pwd;
|
||||
data["pwd"] = pwd;
|
||||
}
|
||||
if (keyId != null) {
|
||||
data["pubKeyId"] = keyId;
|
||||
@@ -123,6 +138,9 @@ class ServerPrivateInfo {
|
||||
if (wolCfg != null) {
|
||||
data["wolCfg"] = wolCfg?.toJson();
|
||||
}
|
||||
if (envs != null) {
|
||||
data["envs"] = envs;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,14 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
|
||||
jumpId: fields[9] as String?,
|
||||
custom: fields[10] as ServerCustom?,
|
||||
wolCfg: fields[11] as WakeOnLanCfg?,
|
||||
envs: (fields[12] as Map?)?.cast<String, String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ServerPrivateInfo obj) {
|
||||
writer
|
||||
..writeByte(12)
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
@@ -59,7 +60,9 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
|
||||
..writeByte(10)
|
||||
..write(obj.custom)
|
||||
..writeByte(11)
|
||||
..write(obj.wolCfg);
|
||||
..write(obj.wolCfg)
|
||||
..writeByte(12)
|
||||
..write(obj.envs);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/data/model/server/battery.dart';
|
||||
import 'package:toolbox/data/model/server/nvdia.dart';
|
||||
import 'package:toolbox/data/model/server/sensors.dart';
|
||||
import 'package:toolbox/data/model/server/server.dart';
|
||||
import 'package:toolbox/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/server/battery.dart';
|
||||
import 'package:server_box/data/model/server/nvdia.dart';
|
||||
import 'package:server_box/data/model/server/sensors.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
|
||||
import '../app/shell_func.dart';
|
||||
import 'cpu.dart';
|
||||
@@ -71,6 +71,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
try {
|
||||
final cpus = SingleCpuCore.parse(StatusCmdType.cpu.find(segments));
|
||||
req.ss.cpu.update(cpus);
|
||||
final brand = CpuBrand.parse(StatusCmdType.cpuBrand.find(segments));
|
||||
req.ss.cpu.brand.clear();
|
||||
req.ss.cpu.brand.addAll(brand);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_lib/fl_lib.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';
|
||||
|
||||
@@ -53,19 +57,116 @@ class Snippet implements TagPickable {
|
||||
@override
|
||||
String get tagName => name;
|
||||
|
||||
String fmtWith(ServerPrivateInfo spi) {
|
||||
final fmted = script.replaceAllMapped(
|
||||
RegExp(r'\${.+?}'),
|
||||
static final fmtFinder = RegExp(r'\$\{[^{}]+\}');
|
||||
|
||||
String fmtWithSpi(ServerPrivateInfo spi) {
|
||||
return script.replaceAllMapped(
|
||||
fmtFinder,
|
||||
(match) {
|
||||
final key = match.group(0);
|
||||
final func = fmtArgs[key];
|
||||
if (func == null) {
|
||||
return key!;
|
||||
}
|
||||
return func(spi);
|
||||
if (func != null) return func(spi);
|
||||
// If not found, return the original content for further processing
|
||||
return key ?? '';
|
||||
},
|
||||
);
|
||||
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 = {
|
||||
@@ -76,6 +177,12 @@ class Snippet implements TagPickable {
|
||||
r'${id}': (ServerPrivateInfo spi) => spi.id,
|
||||
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 {
|
||||
@@ -89,3 +196,32 @@ class SnippetResult {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
linux._(linuxSign),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class TryLimiter {
|
||||
final Map<String, int> _triedTimes = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 {
|
||||
List<SftpName>? files;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
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 '../server/server_private_info.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
part 'virtual_key.g.dart';
|
||||
|
||||
@@ -3,13 +3,6 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppProvider extends ChangeNotifier {
|
||||
int? _newestBuild;
|
||||
int? get newestBuild => _newestBuild;
|
||||
set newestBuild(int? build) {
|
||||
_newestBuild = build;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
BuildContext? ctx;
|
||||
|
||||
bool isWearOS = false;
|
||||
|
||||
@@ -4,13 +4,13 @@ import 'dart:convert';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||
import 'package:toolbox/data/model/container/image.dart';
|
||||
import 'package:toolbox/data/model/container/ps.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/model/container/type.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/core/extension/ssh_client.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/container/image.dart';
|
||||
import 'package:server_box/data/model/container/ps.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/container/type.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
final _dockerNotFound =
|
||||
RegExp(r"command not found|Unknown command|Command '\w+' not found");
|
||||
@@ -26,7 +26,8 @@ class ContainerProvider extends ChangeNotifier {
|
||||
ContainerErr? error;
|
||||
String? runLog;
|
||||
ContainerType type;
|
||||
bool sudo = false;
|
||||
var sudoCompleter = Completer<bool>();
|
||||
bool isBusy = false;
|
||||
|
||||
ContainerProvider({
|
||||
required this.client,
|
||||
@@ -41,6 +42,7 @@ class ContainerProvider extends ChangeNotifier {
|
||||
this.type = type;
|
||||
Stores.container.setType(type, hostId);
|
||||
error = runLog = items = images = version = null;
|
||||
sudoCompleter = Completer<bool>();
|
||||
notifyListeners();
|
||||
await refresh();
|
||||
}
|
||||
@@ -60,17 +62,27 @@ class ContainerProvider extends ChangeNotifier {
|
||||
// return value;
|
||||
// }
|
||||
|
||||
Future<bool> _requiresSudo() async {
|
||||
final psResult = await client?.run(_wrap(ContainerCmdType.ps.exec(type)));
|
||||
if (psResult == null) return true;
|
||||
if (psResult.string.toLowerCase().contains("permission denied")) {
|
||||
return true;
|
||||
void _requiresSudo() async {
|
||||
/// Podman is rootless
|
||||
if (type == ContainerType.podman) return sudoCompleter.complete(false);
|
||||
if (!Stores.setting.containerTrySudo.fetch()) {
|
||||
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 {
|
||||
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.
|
||||
/// Or this will ask for pwd again and again.
|
||||
@@ -78,17 +90,22 @@ class ContainerProvider extends ChangeNotifier {
|
||||
final includeStats = Stores.setting.containerParseStat.fetch();
|
||||
|
||||
var raw = '';
|
||||
final cmd = _wrap(ContainerCmdType.execAll(
|
||||
type,
|
||||
sudo: sudo,
|
||||
includeStats: includeStats,
|
||||
));
|
||||
final code = await client?.execWithPwd(
|
||||
_wrap(ContainerCmdType.execAll(
|
||||
type,
|
||||
sudo: sudo,
|
||||
includeStats: includeStats,
|
||||
)),
|
||||
cmd,
|
||||
context: context,
|
||||
onStdout: (data, _) => raw = '$raw$data',
|
||||
id: hostId,
|
||||
);
|
||||
|
||||
isBusy = false;
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
/// Code 127 means command not found
|
||||
if (code == 127 || raw.contains(_dockerNotFound)) {
|
||||
error = ContainerErr(type: ContainerErrType.notInstalled);
|
||||
@@ -126,8 +143,12 @@ class ContainerProvider extends ChangeNotifier {
|
||||
final psRaw = ContainerCmdType.ps.find(segments);
|
||||
try {
|
||||
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);
|
||||
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList();
|
||||
items = lines.map((e) => ContainerPs.fromRaw(e, type)).toList();
|
||||
} catch (e, trace) {
|
||||
error = ContainerErr(
|
||||
type: ContainerErrType.parsePs,
|
||||
@@ -139,11 +160,18 @@ class ContainerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Parse images
|
||||
final imageRaw = ContainerCmdType.images.find(segments);
|
||||
final imageRaw = ContainerCmdType.images.find(segments).trim();
|
||||
final isEntireJson = imageRaw.startsWith('[') && imageRaw.endsWith(']');
|
||||
try {
|
||||
final imgLines = imageRaw.split('\n');
|
||||
imgLines.removeWhere((element) => element.isEmpty);
|
||||
images = imgLines.map((e) => ContainerImg.fromRawJson(e, type)).toList();
|
||||
if (isEntireJson) {
|
||||
images = (json.decode(imageRaw) as List)
|
||||
.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) {
|
||||
error = ContainerErr(
|
||||
type: ContainerErrType.parseImages,
|
||||
@@ -203,7 +231,7 @@ class ContainerProvider extends ChangeNotifier {
|
||||
runLog = '';
|
||||
final errs = <String>[];
|
||||
final code = await client?.execWithPwd(
|
||||
_wrap(sudo ? 'sudo -S $cmd' : cmd),
|
||||
_wrap((await sudoCompleter.future) ? 'sudo -S $cmd' : cmd),
|
||||
context: context,
|
||||
onStdout: (data, _) {
|
||||
runLog = '$runLog$data';
|
||||
@@ -254,7 +282,16 @@ enum ContainerCmdType {
|
||||
final prefix = sudo ? 'sudo -S ${type.name}' : type.name;
|
||||
return switch (this) {
|
||||
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 =>
|
||||
includeStats ? '$prefix stats --no-stream $_jsonFmt' : 'echo PASS',
|
||||
ContainerCmdType.images => '$prefix image ls $_jsonFmt',
|
||||
@@ -268,6 +305,6 @@ enum ContainerCmdType {
|
||||
}) {
|
||||
return ContainerCmdType.values
|
||||
.map((e) => e.exec(type, sudo: sudo, includeStats: includeStats))
|
||||
.join(' && echo ${ShellFunc.seperator} && ');
|
||||
.join('\necho ${ShellFunc.seperator}\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class PrivateKeyProvider extends ChangeNotifier {
|
||||
List<PrivateKeyInfo> get pkis => _pkis;
|
||||
|
||||
@@ -6,24 +6,28 @@ import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/model/server/pve.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/server/pve.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);
|
||||
|
||||
final class PveProvider extends ChangeNotifier {
|
||||
final ServerPrivateInfo spi;
|
||||
late final String addr;
|
||||
//late final SSHClient _client;
|
||||
late String addr;
|
||||
late final SSHClient _client;
|
||||
late final ServerSocket _serverSocket;
|
||||
final List<SSHForwardChannel> _forwards = [];
|
||||
int _localPort = 0;
|
||||
|
||||
PveProvider({required this.spi}) {
|
||||
// final client = _spi.server?.client;
|
||||
// if (client == null) {
|
||||
// throw Exception('Server client is null');
|
||||
// }
|
||||
// _client = client;
|
||||
final client = spi.server?.client;
|
||||
if (client == null) {
|
||||
throw Exception('Server client is null');
|
||||
}
|
||||
_client = client;
|
||||
final addr = spi.custom?.pveAddr;
|
||||
if (addr == null) {
|
||||
err.value = 'PVE address is null';
|
||||
@@ -41,6 +45,7 @@ final class PveProvider extends ChangeNotifier {
|
||||
..httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.connectionFactory = cf;
|
||||
if (_ignoreCert) {
|
||||
client.badCertificateCallback = (_, __, ___) => true;
|
||||
}
|
||||
@@ -50,55 +55,76 @@ final class PveProvider extends ChangeNotifier {
|
||||
);
|
||||
|
||||
final data = ValueNotifier<PveRes?>(null);
|
||||
|
||||
bool get onlyOneNode => data.value?.nodes.length == 1;
|
||||
String? release;
|
||||
bool isBusy = false;
|
||||
|
||||
// int _localPort = 0;
|
||||
// String get addr => 'http://127.0.0.1:$_localPort';
|
||||
|
||||
Future<void> _init() async {
|
||||
try {
|
||||
//await _forward();
|
||||
await _forward();
|
||||
await _login();
|
||||
await _release;
|
||||
await _getRelease();
|
||||
} on PveErr {
|
||||
err.value = l10n.pveLoginFailed;
|
||||
} catch (e) {
|
||||
Loggers.app.warning('PVE init failed', e);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('PVE init failed', e, s);
|
||||
err.value = e.toString();
|
||||
} finally {
|
||||
connected.complete();
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> _forward() async {
|
||||
// var retries = 0;
|
||||
// while (retries < 3) {
|
||||
// try {
|
||||
// _localPort = Random().nextInt(1000) + 37000;
|
||||
// print('Forwarding local port $_localPort');
|
||||
// final serverSocket = await ServerSocket.bind('localhost', _localPort);
|
||||
// final forward = await _client.forwardLocal('127.0.0.1', 8006);
|
||||
// serverSocket.listen((socket) {
|
||||
// forward.stream.cast<List<int>>().pipe(socket);
|
||||
// socket.pipe(forward.sink);
|
||||
// });
|
||||
// return;
|
||||
// } on SocketException {
|
||||
// retries++;
|
||||
// }
|
||||
// }
|
||||
// throw Exception('Failed to bind local port');
|
||||
// }
|
||||
Future<void> _forward() async {
|
||||
final url = Uri.parse(addr);
|
||||
if (_localPort == 0) {
|
||||
_serverSocket = await ServerSocket.bind('localhost', 0);
|
||||
_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);
|
||||
});
|
||||
final newUrl = Uri.parse(addr)
|
||||
.replace(host: 'localhost', port: _localPort)
|
||||
.toString();
|
||||
debugPrint('Forwarding $newUrl to $addr');
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
final resp = await session.post('$addr/api2/extjs/access/ticket', data: {
|
||||
'username': spi.user,
|
||||
'password': spi.pwd,
|
||||
'realm': 'pam',
|
||||
'new-format': '1'
|
||||
});
|
||||
final resp = await session.post(
|
||||
'$addr/api2/extjs/access/ticket',
|
||||
data: {
|
||||
'username': spi.user,
|
||||
'password': spi.pwd,
|
||||
'realm': 'pam',
|
||||
'new-format': '1'
|
||||
},
|
||||
options: Options(
|
||||
headers: {HttpHeaders.contentTypeHeader: Headers.jsonContentType},
|
||||
),
|
||||
);
|
||||
try {
|
||||
final ticket = resp.data['data']['ticket'];
|
||||
session.options.headers['CSRFPreventionToken'] =
|
||||
@@ -110,7 +136,7 @@ final class PveProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// 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 version = resp.data['data']['release'] as String?;
|
||||
if (version != null) {
|
||||
@@ -167,4 +193,13 @@ final class PveProvider extends ChangeNotifier {
|
||||
bool _isCtrlSuc(Response resp) {
|
||||
return resp.statusCode == 200;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
await _serverSocket.close();
|
||||
for (final forward in _forwards) {
|
||||
forward.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
// import 'dart:io';
|
||||
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||
import 'package:toolbox/core/utils/ssh_auth.dart';
|
||||
import 'package:toolbox/data/model/app/error.dart';
|
||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||
import 'package:toolbox/data/model/server/system.dart';
|
||||
import 'package:toolbox/data/model/sftp/req.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/core/extension/ssh_client.dart';
|
||||
import 'package:server_box/core/utils/ssh_auth.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/app/shell_func.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
// import 'package:server_box/data/model/sftp/req.dart';
|
||||
// import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
import '../../core/utils/server.dart';
|
||||
import '../model/server/server.dart';
|
||||
import '../model/server/server_private_info.dart';
|
||||
import '../model/server/server_status_update_req.dart';
|
||||
import '../model/server/snippet.dart';
|
||||
import '../model/server/try_limiter.dart';
|
||||
import '../res/status.dart';
|
||||
|
||||
@@ -314,11 +313,10 @@ class ServerProvider extends ChangeNotifier {
|
||||
|
||||
_setServerState(s, ServerConn.connected);
|
||||
|
||||
// Write script to server
|
||||
// by ssh
|
||||
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
|
||||
|
||||
try {
|
||||
await s.client?.runForOutput(
|
||||
await s.client!.runForOutput(
|
||||
ShellFunc.installShellCmd,
|
||||
action: (session) async {
|
||||
session.stdin.add(scriptRaw);
|
||||
@@ -336,40 +334,11 @@ class ServerProvider extends ChangeNotifier {
|
||||
_setServerState(s, ServerConn.failed);
|
||||
return;
|
||||
} catch (e) {
|
||||
Loggers.app.warning('Write script to ${spi.name} by shell', e);
|
||||
|
||||
/// by sftp
|
||||
final localPath = Paths.doc.joinPath('install.sh');
|
||||
final file = File(localPath);
|
||||
try {
|
||||
file.writeAsBytes(scriptRaw);
|
||||
final completer = Completer();
|
||||
final homePath = (await s.client?.run('echo \$HOME').string)?.trim();
|
||||
if (homePath == null || homePath.isEmpty) {
|
||||
throw Exception('Got empty home path');
|
||||
}
|
||||
final remotePath = ShellFunc.getShellPath(homePath);
|
||||
final reqId = Pros.sftp.add(
|
||||
SftpReq(spi, remotePath, localPath, SftpReqType.upload),
|
||||
completer: completer,
|
||||
);
|
||||
await completer.future;
|
||||
final err = Pros.sftp.get(reqId)?.error;
|
||||
if (err != null) {
|
||||
throw err;
|
||||
}
|
||||
} catch (ee) {
|
||||
TryLimiter.inc(sid);
|
||||
s.status.err = SSHErr(
|
||||
type: SSHErrType.writeScript,
|
||||
message: '$e\n\n$ee',
|
||||
);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
Loggers.app.warning('Write script to ${spi.name} by sftp', ee);
|
||||
return;
|
||||
} finally {
|
||||
if (await file.exists()) await file.delete();
|
||||
}
|
||||
final err = e.toString();
|
||||
TryLimiter.inc(sid);
|
||||
s.status.err = SSHErr(type: SSHErrType.writeScript, message: err);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
Loggers.app.warning('Write script to ${spi.name} by shell', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,26 +428,4 @@ class ServerProvider extends ChangeNotifier {
|
||||
// reset try times only after prepared successfully
|
||||
TryLimiter.reset(sid);
|
||||
}
|
||||
|
||||
Future<SnippetResult?> runSnippet(String id, Snippet snippet) async {
|
||||
final server = _servers[id];
|
||||
if (server == null) return null;
|
||||
final watch = Stopwatch()..start();
|
||||
final result = await server.client?.run(snippet.fmtWith(server.spi)).string;
|
||||
final time = watch.elapsed;
|
||||
watch.stop();
|
||||
if (result == null) return null;
|
||||
return SnippetResult(
|
||||
dest: _servers[id]?.spi.name,
|
||||
result: result,
|
||||
time: time,
|
||||
);
|
||||
}
|
||||
|
||||
// Future<List<SnippetResult?>> runSnippetsMulti(
|
||||
// List<String> ids,
|
||||
// Snippet snippet,
|
||||
// ) async {
|
||||
// return await Future.wait(ids.map((id) async => runSnippet(id, snippet)));
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:convert';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/data/model/server/snippet.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class SnippetProvider extends ChangeNotifier {
|
||||
late List<Snippet> _snippets;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {
|
||||
|
||||
@@ -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 {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 918;
|
||||
static const String engine = "3.22.1";
|
||||
static const String buildAt = "2024-05-25 19:17:18";
|
||||
static const int modifications = 2;
|
||||
static const int script = 48;
|
||||
static const int build = 1034;
|
||||
static const int script = 54;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import 'package:toolbox/data/model/app/github_id.dart';
|
||||
|
||||
abstract final class GithubIds {
|
||||
// Thanks
|
||||
// If you want to change your Github ID, please open an issue.
|
||||
static const contributors = <GhId>{
|
||||
'PaperCube',
|
||||
'Integral-Tech',
|
||||
'its-tom',
|
||||
'leganck',
|
||||
'azkadev',
|
||||
'kalashnikov',
|
||||
'FrancXPT',
|
||||
'RainSunMe',
|
||||
'calvinweb',
|
||||
'No06',
|
||||
'QazCetelic',
|
||||
'RainSunMe',
|
||||
'FrancXPT',
|
||||
'Liloupar',
|
||||
'dccif',
|
||||
'QazCetelic',
|
||||
};
|
||||
|
||||
static const participants = <GhId>{
|
||||
'jaychoubaby',
|
||||
'fecture',
|
||||
'Tao173',
|
||||
'Jasonzhu1207',
|
||||
'QingAnLe',
|
||||
'wxdjs',
|
||||
'Aeorq',
|
||||
@@ -72,6 +74,32 @@ abstract final class GithubIds {
|
||||
'pgs666',
|
||||
'FHU-yezi',
|
||||
'ZRY233',
|
||||
'Jasonzhu1207',
|
||||
'sakuraanzu',
|
||||
'licaon-kter',
|
||||
'77160860',
|
||||
'mijjjj',
|
||||
'muyunil',
|
||||
'Hua159',
|
||||
'jaydong2016',
|
||||
'geol',
|
||||
'Mooling0602',
|
||||
'IllTamer',
|
||||
'marlkiller',
|
||||
'hlarc',
|
||||
'itsandrewpao',
|
||||
'StudyingLover',
|
||||
'QJAG1024',
|
||||
'Wuming-HUST',
|
||||
'WolfCanglong',
|
||||
'liwenjie119',
|
||||
'logce',
|
||||
'h-lyf',
|
||||
'88484396',
|
||||
};
|
||||
}
|
||||
|
||||
typedef GhId = String;
|
||||
|
||||
extension GhIdX on GhId {
|
||||
String get url => 'https://github.com/$this';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
abstract final class Miscs {
|
||||
static final blankReg = RegExp(r'\s+');
|
||||
static final multiBlankreg = RegExp(r'\s{2,}');
|
||||
|
||||
/// RegExp for password request
|
||||
static final pwdRequestWithUserReg = RegExp(r'\[sudo\] password for (.+):');
|
||||
@@ -18,4 +19,6 @@ abstract final class Miscs {
|
||||
static const pkgName = 'tech.lolli.toolbox';
|
||||
|
||||
static const jsonEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
static const bakFileName = 'srvbox_bak.json';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/provider/private_key.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/sftp.dart';
|
||||
import 'package:toolbox/data/provider/snippet.dart';
|
||||
import 'package:server_box/data/provider/app.dart';
|
||||
import 'package:server_box/data/provider/private_key.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/provider/sftp.dart';
|
||||
import 'package:server_box/data/provider/snippet.dart';
|
||||
|
||||
abstract final class Pros {
|
||||
static final app = AppProvider();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:toolbox/data/model/app/rebuild.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
|
||||
abstract final class RebuildNodes {
|
||||
static final app = RebuildNode();
|
||||
abstract final class RNodes {
|
||||
static final app = RNode();
|
||||
static final dark = false.vn;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:toolbox/data/model/server/server.dart';
|
||||
import 'package:toolbox/data/model/server/temp.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/model/server/temp.dart';
|
||||
|
||||
import '../model/server/cpu.dart';
|
||||
import '../model/server/disk.dart';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/data/store/container.dart';
|
||||
import 'package:toolbox/data/store/history.dart';
|
||||
import 'package:toolbox/data/store/private_key.dart';
|
||||
import 'package:toolbox/data/store/server.dart';
|
||||
import 'package:toolbox/data/store/setting.dart';
|
||||
import 'package:toolbox/data/store/snippet.dart';
|
||||
import 'package:server_box/data/store/container.dart';
|
||||
import 'package:server_box/data/store/history.dart';
|
||||
import 'package:server_box/data/store/private_key.dart';
|
||||
import 'package:server_box/data/store/server.dart';
|
||||
import 'package:server_box/data/store/setting.dart';
|
||||
import 'package:server_box/data/store/snippet.dart';
|
||||
|
||||
abstract final class Stores {
|
||||
static final setting = SettingStore();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
abstract final class Urls {
|
||||
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 appHelp = '$myGithub/flutter_server_box#-help';
|
||||
static const appWiki = '$myGithub/flutter_server_box/wiki';
|
||||
static const thisRepo = '$myGithub/flutter_server_box';
|
||||
static const appHelp = '$thisRepo#-help';
|
||||
static const appWiki = '$thisRepo/wiki';
|
||||
static const analysis = 'https://countly.lolli.tech';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/data/model/container/type.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:server_box/data/model/container/type.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
const _keyConfig = 'providerConfig';
|
||||
|
||||
|
||||
@@ -54,4 +54,7 @@ class HistoryStore extends PersistentStore {
|
||||
late final sftpLastPath = _MapHistory(box: box, name: 'sftpLastPath');
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:toolbox/data/model/app/menu/server_func.dart';
|
||||
import 'package:toolbox/data/model/app/server_detail_card.dart';
|
||||
import 'package:toolbox/data/model/ssh/virtual_key.dart';
|
||||
import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||
import 'package:server_box/data/model/app/server_detail_card.dart';
|
||||
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||
|
||||
import '../model/app/net_view.dart';
|
||||
import '../res/default.dart';
|
||||
@@ -265,8 +265,6 @@ class SettingStore extends PersistentStore {
|
||||
|
||||
late final horizonVirtKey = property('horizonVirtKey', false);
|
||||
|
||||
late final collectUsage = property('collectUsage', true);
|
||||
|
||||
/// general wake lock
|
||||
late final generalWakeLock = property('generalWakeLock', false);
|
||||
|
||||
@@ -276,6 +274,20 @@ class SettingStore extends PersistentStore {
|
||||
/// fmt: https://example.com/{DIST}-{BRIGHT}.png
|
||||
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 introVer = property('introVer', 0);
|
||||
|
||||
late final letterCache = property('letterCache', false);
|
||||
|
||||
/// Set it to `$EDITOR`, `vim` and etc. to use remote system editor in SSH terminal.
|
||||
/// Set it empty to use local editor GUI.
|
||||
late final sftpEditor = property('sftpEditor', '');
|
||||
|
||||
// Never show these settings for users
|
||||
//
|
||||
// ------BEGIN------
|
||||
|
||||
129
lib/intro.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
part of 'app.dart';
|
||||
|
||||
final class _IntroPage extends StatelessWidget {
|
||||
final List<IntroPageBuilder> pages;
|
||||
|
||||
const _IntroPage(this.pages);
|
||||
|
||||
static const _builders = {
|
||||
1: _buildAppSettings,
|
||||
2: _buildRecommended,
|
||||
1006: _buildTermLetterCache,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, cons) {
|
||||
final padTop = cons.maxHeight * .16;
|
||||
final pages_ = pages.map((e) => e(context, padTop)).toList();
|
||||
return IntroPage(
|
||||
args: IntroPageArgs(
|
||||
pages: pages_,
|
||||
onDone: (ctx) {
|
||||
Stores.setting.introVer.put(BuildData.build);
|
||||
Navigator.of(ctx).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const HomePage()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildTermLetterCache(BuildContext context, double padTop) {
|
||||
return ListView(
|
||||
padding: _introListPad,
|
||||
children: [
|
||||
SizedBox(height: padTop),
|
||||
IntroPage.title(icon: BoxIcons.bxs_terminal, big: true),
|
||||
SizedBox(height: padTop),
|
||||
ListTile(
|
||||
leading: const Icon(Bootstrap.alphabet),
|
||||
title: Text(l10n.letterCache),
|
||||
subtitle: Text(l10n.letterCacheTip, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.letterCache),
|
||||
).cardx,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildRecommended(BuildContext context, double padTop) {
|
||||
return ListView(
|
||||
padding: _introListPad,
|
||||
children: [
|
||||
SizedBox(height: padTop),
|
||||
IntroPage.title(icon: Bootstrap.stars, big: true),
|
||||
SizedBox(height: padTop),
|
||||
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(MingCute.chart_line_line, size: _kIconSize),
|
||||
title: Text(l10n.stat),
|
||||
subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.containerParseStat),
|
||||
).cardx,
|
||||
ListTile(
|
||||
leading: const Icon(OctIcons.cpu),
|
||||
title: Text(l10n.noLineChartForCpu),
|
||||
subtitle: Text(l10n.cpuViewAsProgressTip, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.cpuViewAsProgress),
|
||||
).cardx,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildAppSettings(BuildContext ctx, double padTop) {
|
||||
return ListView(
|
||||
padding: _introListPad,
|
||||
children: [
|
||||
SizedBox(height: padTop),
|
||||
IntroPage.title(text: 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.nativeDisplayLanguage} (${p0.code})',
|
||||
initial: _setting.locale.fetch().toLocale,
|
||||
);
|
||||
if (selected != null) {
|
||||
_setting.locale.put(selected.code);
|
||||
RNodes.app.notify();
|
||||
}
|
||||
},
|
||||
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,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static List<IntroPageBuilder> get builders {
|
||||
final storedVer = _setting.introVer.fetch();
|
||||
return _builders.entries
|
||||
.where((e) => e.key > storedVer)
|
||||
.map((e) => e.value)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static final _setting = Stores.setting;
|
||||
static const _kIconSize = 23.0;
|
||||
static const _introListPad = EdgeInsets.symmetric(horizontal: 17);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"@@locale": "de",
|
||||
"about": "Über",
|
||||
"aboutThanks": "Vielen Dank an die folgenden Personen, die daran teilgenommen haben.\n",
|
||||
"acceptBeta": "Akzeptieren Sie Testversion-Updates",
|
||||
"add": "Neu",
|
||||
"addAServer": "Server hinzufügen",
|
||||
"addPrivateKey": "Private key hinzufügen",
|
||||
@@ -10,7 +11,6 @@
|
||||
"addr": "Adresse",
|
||||
"all": "Alle",
|
||||
"alreadyLastDir": "Bereits im letzten Verzeichnis.",
|
||||
"alterUrl": "Url ändern",
|
||||
"askContinue": "{msg}. Weiter?",
|
||||
"attention": "Achtung",
|
||||
"authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.",
|
||||
@@ -44,7 +44,6 @@
|
||||
"cnKeyboardCompTip": "Wenn das Terminal ein sicheres Tastenfeld öffnet, können Sie es aktivieren.",
|
||||
"collapseUI": "Zusammenbrechen",
|
||||
"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",
|
||||
"connected": "in Verbindung gebracht",
|
||||
"container": "Container",
|
||||
@@ -68,7 +67,6 @@
|
||||
"decode": "Decode",
|
||||
"decompress": "Dekomprimieren",
|
||||
"delete": "Löschen",
|
||||
"deleteScripts": "Gleichzeitiges Löschen von Server-Skripten",
|
||||
"deleteServers": "Batch-Löschung von Servern",
|
||||
"deviceName": "Gerätename",
|
||||
"dirEmpty": "Stelle sicher, dass der Ordner leer ist.",
|
||||
@@ -94,12 +92,15 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.",
|
||||
"encode": "Encode",
|
||||
"envVars": "Umgebungsvariable",
|
||||
"error": "Fehler",
|
||||
"exampleName": "Servername",
|
||||
"experimentalFeature": "Experimentelles Feature",
|
||||
"export": "Export",
|
||||
"extraArgs": "Extra args",
|
||||
"failed": "Failed",
|
||||
"fallbackSshDest": "SSH-Fallback-Ziel",
|
||||
"fdroidReleaseTip": "Wenn Sie diese App von F-Droid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.",
|
||||
"feedback": "Feedback",
|
||||
"feedbackOnGithub": "Wenn du Fragen hast, stelle diese bitte auf Github.",
|
||||
"fieldMustNotEmpty": "Die Eingabefelder dürfen nicht leer sein.",
|
||||
@@ -110,6 +111,7 @@
|
||||
"followSystem": "System verfolgen",
|
||||
"font": "Schriftarten",
|
||||
"fontSize": "Schriftgröße",
|
||||
"forExample": "Zum Beispiel",
|
||||
"force": "freiwillig",
|
||||
"foundNUpdate": "Update {count} gefunden",
|
||||
"fullScreen": "Vollbildmodus",
|
||||
@@ -133,6 +135,7 @@
|
||||
"imagesList": "Images",
|
||||
"import": "Importieren",
|
||||
"inAppUpdate": "Im App aktualisieren? Andernfalls mit einem Browser herunterladen.",
|
||||
"init": "Initialisieren",
|
||||
"inner": "Eingebaut",
|
||||
"inputDomainHere": "Domain eingeben",
|
||||
"install": "install",
|
||||
@@ -151,6 +154,8 @@
|
||||
"languageName": "Deutsch",
|
||||
"lastTry": "Letzter Versuch",
|
||||
"launchPage": "Startseite",
|
||||
"letterCache": "Buchstaben-Caching",
|
||||
"letterCacheTip": "Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.",
|
||||
"license": "Lizenzen",
|
||||
"light": "Hell",
|
||||
"loadingFiles": "Lädt Dateien...",
|
||||
@@ -171,12 +176,13 @@
|
||||
"name": "Name",
|
||||
"needHomeDir": "Wenn Sie ein Synology-Benutzer sind, [sehen Sie hier](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Benutzer anderer Systeme müssen suchen, wie man ein Home-Verzeichnis erstellt.",
|
||||
"needRestart": "App muss neugestartet werden",
|
||||
"net": "Netz",
|
||||
"net": "Netzwerk",
|
||||
"netViewType": "Netzwerkansicht Typ",
|
||||
"newContainer": "Neuer Container",
|
||||
"noClient": "Kein Client",
|
||||
"noInterface": "Kein Interface",
|
||||
"noLineChart": "Verwenden Sie keine Liniendiagramme",
|
||||
"noLineChartForCpu": "Verwenden Sie keine Liniendiagramme für CPU",
|
||||
"noNotiPerm": "Keine Benachrichtigungsrechte, möglicherweise keine Fortschrittsanzeige beim Herunterladen von App-Updates.",
|
||||
"noOptions": "Keine Optionen verfügbar",
|
||||
"noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.",
|
||||
@@ -199,11 +205,11 @@
|
||||
"open": "Öffnen",
|
||||
"openLastPath": "Öffnen Sie den letzten Pfad",
|
||||
"openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang",
|
||||
"parseContainerStats": "Den Status der Container-Belegung analysieren",
|
||||
"parseContainerStatsTip": "Das Analysieren des Belegungsstatus durch Docker ist relativ langsam",
|
||||
"paste": "Einfügen",
|
||||
"path": "Pfad",
|
||||
"percentOfSize": "{percent}% von {size}",
|
||||
"permission": "Berechtigungen",
|
||||
"pickFile": "Datei wählen",
|
||||
"pingAvg": "Avg:",
|
||||
"pingInputIP": "Bitte gib eine Ziel-IP/Domain ein.",
|
||||
@@ -229,6 +235,7 @@
|
||||
"rememberChoice": "Auswahl merken",
|
||||
"rememberPwdInMem": "Passwort im Speicher behalten",
|
||||
"rememberPwdInMemTip": "Für Container, Aufhängen usw.",
|
||||
"rememberWindowSize": "Fenstergröße merken",
|
||||
"remotePath": "Entfernte Pfade",
|
||||
"rename": "Umbenennen",
|
||||
"reportBugsOnGithubIssue": "Bitte Bugs auf {url} melden",
|
||||
@@ -257,6 +264,7 @@
|
||||
"serverTabUnkown": "Unbekannter Status",
|
||||
"setting": "Einstellungen",
|
||||
"sftpDlPrepare": "Verbindung vorbereiten...",
|
||||
"sftpEditorTip": "Wenn leer, verwenden Sie den im App integrierten Dateieditor. Wenn ein Wert vorhanden ist, wird der Editor des Remote-Servers verwendet, z.B. `vim` (es wird empfohlen, automatisch gemäß `EDITOR` zu ermitteln).",
|
||||
"sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.",
|
||||
"sftpSSHConnected": "SFTP Verbunden",
|
||||
"sftpShowFoldersFirst": "Ordner zuerst anzeigen",
|
||||
@@ -271,6 +279,7 @@
|
||||
"sshTip": "Diese Funktion befindet sich jetzt in der Experimentierphase.\n\nBitte melde Bugs auf {url} oder mach mit bei der Entwicklung.",
|
||||
"sshVirtualKeyAutoOff": "Automatische Umschaltung der virtuellen Tasten",
|
||||
"start": "Start",
|
||||
"stat": "Statistik",
|
||||
"stats": "Statistik",
|
||||
"stop": "Stop",
|
||||
"stopped": "Ausgelaufen",
|
||||
@@ -280,6 +289,7 @@
|
||||
"suspend": "Suspend",
|
||||
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
|
||||
"switchTo": "Wechseln zu {val}",
|
||||
"sync": "Sync",
|
||||
"syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.",
|
||||
"system": "Systeme",
|
||||
"tag": "Tags",
|
||||
@@ -296,7 +306,7 @@
|
||||
"total": "Total",
|
||||
"traffic": "Durchflussmenge",
|
||||
"trySudo": "Versuche es mit sudo",
|
||||
"ttl": "ttl",
|
||||
"ttl": "TTL",
|
||||
"unknown": "Unbekannt",
|
||||
"unknownError": "Unbekannter Fehler",
|
||||
"unkownConvertMode": "Unbekannter Konvertierungsmodus",
|
||||
@@ -332,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "Wird sofort angewendet",
|
||||
"wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.",
|
||||
"write": "Schreiben",
|
||||
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht."
|
||||
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
|
||||
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in ~/.config/server_box geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen."
|
||||
}
|
||||
@@ -2,31 +2,31 @@
|
||||
"@@locale": "en",
|
||||
"about": "About",
|
||||
"aboutThanks": "Thanks to the following people who participated in.",
|
||||
"acceptBeta": "Accept beta version updates",
|
||||
"add": "Add",
|
||||
"addAServer": "add a server",
|
||||
"addPrivateKey": "Add private key",
|
||||
"addSystemPrivateKeyTip": "Currently don't have any private key, do you add the one that comes with the system (~/.ssh/id_rsa)?",
|
||||
"addSystemPrivateKeyTip": "Currently private keys don't exist, do you want to add the one that comes with the system (~/.ssh/id_rsa)?",
|
||||
"added2List": "Added to task list",
|
||||
"addr": "Address",
|
||||
"all": "All",
|
||||
"alreadyLastDir": "Already in last directory.",
|
||||
"alterUrl": "Alter url",
|
||||
"askContinue": "{msg}. Continue?",
|
||||
"attention": "Attention",
|
||||
"authFailTip": "Authentication failed, please check if the password/key/host/user, etc., are incorrect.",
|
||||
"authFailTip": "Authentication failed, please check whether credentials are correct",
|
||||
"authRequired": "Auth required",
|
||||
"auto": "Auto",
|
||||
"autoBackupConflict": "Only one automatic backup can be turned on at the same time.",
|
||||
"autoCheckUpdate": "Auto check update",
|
||||
"autoCheckUpdate": "Automatic update check",
|
||||
"autoConnect": "Auto connect",
|
||||
"autoRun": "Automatic Run",
|
||||
"autoUpdateHomeWidget": "Auto update home widget",
|
||||
"autoRun": "Auto run",
|
||||
"autoUpdateHomeWidget": "Automatic home widget update",
|
||||
"backup": "Backup",
|
||||
"backupTip": "The exported data is simply encrypted. \nPlease keep it safe.",
|
||||
"backupTip": "The exported data is weakly encrypted. \nPlease keep it safe.",
|
||||
"backupVersionNotMatch": "Backup version is not match.",
|
||||
"battery": "Battery",
|
||||
"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\".",
|
||||
"bgRun": "Run in background",
|
||||
"bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".",
|
||||
"bioAuth": "Biometric auth",
|
||||
"browser": "Browser",
|
||||
"bulkImportServers": "Batch import servers",
|
||||
@@ -44,7 +44,6 @@
|
||||
"cnKeyboardCompTip": "If the terminal pops up a secure keyboard, you can enable it.",
|
||||
"collapseUI": "Collapse",
|
||||
"collapseUITip": "Whether to collapse long lists present in the UI by default",
|
||||
"collectUsage": "Collect usage information (unrelated to privacy).",
|
||||
"conn": "Connection",
|
||||
"connected": "Connected",
|
||||
"container": "Container",
|
||||
@@ -55,7 +54,7 @@
|
||||
"convert": "Convert",
|
||||
"copy": "Copy",
|
||||
"copyPath": "Copy path",
|
||||
"cpuViewAsProgressTip": "Display the usage rate of each CPU in a progress bar style (old style)",
|
||||
"cpuViewAsProgressTip": "Display the usage of each CPU in a progress bar style (old style)",
|
||||
"createFile": "Create file",
|
||||
"createFolder": "Create folder",
|
||||
"cursorType": "Cursor type",
|
||||
@@ -68,10 +67,9 @@
|
||||
"decode": "Decode",
|
||||
"decompress": "Decompress",
|
||||
"delete": "Delete",
|
||||
"deleteScripts": "Delete server scripts at the same time",
|
||||
"deleteServers": "Batch delete servers",
|
||||
"deviceName": "Device name",
|
||||
"dirEmpty": "Make sure dir is empty.",
|
||||
"dirEmpty": "Make sure the folder is empty.",
|
||||
"disabled": "Disabled",
|
||||
"disconnected": "Disconnected",
|
||||
"disk": "Disk",
|
||||
@@ -92,16 +90,19 @@
|
||||
"edit": "Edit",
|
||||
"editVirtKeys": "Edit virtual keys",
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "The current code highlighting performance is worse and can be optionally turned off to improve.",
|
||||
"editorHighlightTip": "The current code highlighting performance is not ideal and can be optionally turned off to improve.",
|
||||
"encode": "Encode",
|
||||
"envVars": "Environment variable",
|
||||
"error": "Error",
|
||||
"exampleName": "Example name",
|
||||
"experimentalFeature": "Experimental feature",
|
||||
"export": "Export",
|
||||
"extraArgs": "Extra args",
|
||||
"extraArgs": "Extra arguments",
|
||||
"failed": "Failed",
|
||||
"fallbackSshDest": "Fallback SSH destination",
|
||||
"fdroidReleaseTip": "If you downloaded this app from F-Droid, it is recommended to turn off this option.",
|
||||
"feedback": "Feedback",
|
||||
"feedbackOnGithub": "If you have any questions, please feedback on Github.",
|
||||
"feedbackOnGithub": "If you have any questions, please create issues on Github.",
|
||||
"fieldMustNotEmpty": "These fields must not be empty.",
|
||||
"fileNotExist": "{file} not exist",
|
||||
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
|
||||
@@ -110,6 +111,7 @@
|
||||
"followSystem": "Follow system",
|
||||
"font": "Font",
|
||||
"fontSize": "Font size",
|
||||
"forExample": "For example",
|
||||
"force": "Force",
|
||||
"foundNUpdate": "Found {count} update",
|
||||
"fullScreen": "Full screen mode",
|
||||
@@ -122,7 +124,7 @@
|
||||
"goto": "Go to",
|
||||
"hideTitleBar": "Hide title bar",
|
||||
"hideTitleBarTip": "After turning it on, please hold down the three buttons in the top right corner to drag.",
|
||||
"highlight": "Code highlight",
|
||||
"highlight": "Code highlighting",
|
||||
"homeWidgetUrlConfig": "Config home widget url",
|
||||
"host": "Host",
|
||||
"hour": "Hour",
|
||||
@@ -133,6 +135,7 @@
|
||||
"imagesList": "Images list",
|
||||
"import": "Import",
|
||||
"inAppUpdate": "Update within the app? Otherwise, download using a browser.",
|
||||
"init": "Initialize",
|
||||
"inner": "Inner",
|
||||
"inputDomainHere": "Input Domain here",
|
||||
"install": "install",
|
||||
@@ -151,6 +154,8 @@
|
||||
"languageName": "English",
|
||||
"lastTry": "Last try",
|
||||
"launchPage": "Launch page",
|
||||
"letterCache": "Letter caching",
|
||||
"letterCacheTip": "Recommended to disable, but after disabling, it will be impossible to input CJK characters.",
|
||||
"license": "License",
|
||||
"light": "Light",
|
||||
"loadingFiles": "Loading files...",
|
||||
@@ -160,7 +165,7 @@
|
||||
"madeWithLove": "Made with ❤️ by {myGithub}",
|
||||
"manual": "Manual",
|
||||
"max": "max",
|
||||
"maxRetryCount": "Number of server reconnection",
|
||||
"maxRetryCount": "Number of server reconnections",
|
||||
"maxRetryCountEqual0": "Will retry again and again.",
|
||||
"min": "min",
|
||||
"minute": "Minute",
|
||||
@@ -170,13 +175,14 @@
|
||||
"ms": "ms",
|
||||
"name": "Name",
|
||||
"needHomeDir": "If you are a Synology user, [see here](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Users of other systems need to search for how to create a home directory.",
|
||||
"needRestart": "Need to restart app",
|
||||
"net": "Net",
|
||||
"netViewType": "Net view type",
|
||||
"needRestart": "App needs to be restarted",
|
||||
"net": "Network",
|
||||
"netViewType": "Network view type",
|
||||
"newContainer": "New container",
|
||||
"noClient": "No client",
|
||||
"noInterface": "No interface",
|
||||
"noLineChart": "Do not use line charts",
|
||||
"noLineChartForCpu": "Do not use line charts for CPU",
|
||||
"noNotiPerm": "No notification permissions, possibly no progress indication when downloading app updates.",
|
||||
"noOptions": "No options",
|
||||
"noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.",
|
||||
@@ -195,22 +201,22 @@
|
||||
"ok": "OK",
|
||||
"onServerDetailPage": "On server detail page",
|
||||
"onlyOneLine": "Only display as one line (scrollable)",
|
||||
"onlyWhenCoreBiggerThan8": "Works only when the number of cores > 8",
|
||||
"onlyWhenCoreBiggerThan8": "Works only when the number of cores is greater than 8",
|
||||
"open": "Open",
|
||||
"openLastPath": "Open the last path",
|
||||
"openLastPathTip": "Different servers will have different logs, and the log is the path to the exit",
|
||||
"parseContainerStats": "Parse the container occupancy status",
|
||||
"parseContainerStatsTip": "Docker parsing the occupancy status is relatively slow.",
|
||||
"parseContainerStatsTip": "Parsing the occupancy status of Docker is relatively slow.",
|
||||
"paste": "Paste",
|
||||
"path": "Path",
|
||||
"percentOfSize": "{percent}% of {size}",
|
||||
"permission": "Permissions",
|
||||
"pickFile": "Pick file",
|
||||
"pingAvg": "Avg:",
|
||||
"pingInputIP": "Please input a target IP / domain.",
|
||||
"pingNoServer": "No server to ping.\nPlease add a server in server tab.",
|
||||
"pkg": "Pkg",
|
||||
"pkgUpgradeTip": "Please backup your system before updating.",
|
||||
"platformNotSupportUpdate": "Current platform does not support in app update.\nPlease build from source and install it.",
|
||||
"platformNotSupportUpdate": "Current platform does not support in-app update.\nPlease build from source and install it.",
|
||||
"plugInType": "Insertion Type",
|
||||
"plzEnterHost": "Please enter host.",
|
||||
"plzSelectKey": "Please select a key.",
|
||||
@@ -229,6 +235,7 @@
|
||||
"rememberChoice": "Remember the selection",
|
||||
"rememberPwdInMem": "Remember password in memory",
|
||||
"rememberPwdInMemTip": "Used for containers, suspending, etc.",
|
||||
"rememberWindowSize": "Remember window size",
|
||||
"remotePath": "Remote path",
|
||||
"rename": "Rename",
|
||||
"reportBugsOnGithubIssue": "Please report bugs on {url}",
|
||||
@@ -247,7 +254,7 @@
|
||||
"sequence": "Sequence",
|
||||
"server": "Server",
|
||||
"serverDetailOrder": "Detail page widget order",
|
||||
"serverFuncBtns": "Server func buttons",
|
||||
"serverFuncBtns": "Server function buttons",
|
||||
"serverOrder": "Server order",
|
||||
"serverTabConnecting": "Connecting...",
|
||||
"serverTabEmpty": "There is no server.\nClick the fab to add one.",
|
||||
@@ -257,6 +264,7 @@
|
||||
"serverTabUnkown": "Unknown state",
|
||||
"setting": "Settings",
|
||||
"sftpDlPrepare": "Preparing to connect...",
|
||||
"sftpEditorTip": "If empty, use the built-in file editor of the app. If a value is present, use the remote server’s editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.",
|
||||
"sftpSSHConnected": "SFTP Connected",
|
||||
"sftpShowFoldersFirst": "Display folders first",
|
||||
@@ -271,15 +279,17 @@
|
||||
"sshTip": "This function is now in the experimental stage.\n\nPlease report bugs on {url} or join our development.",
|
||||
"sshVirtualKeyAutoOff": "Auto switching of virtual keys",
|
||||
"start": "Start",
|
||||
"stats": "Stats",
|
||||
"stat": "Statistics",
|
||||
"stats": "Statistics",
|
||||
"stop": "Stop",
|
||||
"stopped": "Stopped",
|
||||
"storage": "Storage",
|
||||
"success": "Success",
|
||||
"supportFmtArgs": "The following formatting parameters are supported:",
|
||||
"suspend": "Suspend",
|
||||
"suspendTip": "The suspend function requires root privileges and systemd support.",
|
||||
"suspendTip": "The suspend function requires root permission and systemd support.",
|
||||
"switchTo": "Switch to {val}",
|
||||
"sync": "Sync",
|
||||
"syncTip": "A restart may be required for some changes to take effect.",
|
||||
"system": "System",
|
||||
"tag": "Tags",
|
||||
@@ -296,10 +306,10 @@
|
||||
"total": "Total",
|
||||
"traffic": "Traffic",
|
||||
"trySudo": "Try using sudo",
|
||||
"ttl": "ttl",
|
||||
"ttl": "TTL",
|
||||
"unknown": "Unknown",
|
||||
"unknownError": "Unknown error",
|
||||
"unkownConvertMode": "Unknown convert mode",
|
||||
"unkownConvertMode": "Unknown conversion mode",
|
||||
"update": "Update",
|
||||
"updateAll": "Update all",
|
||||
"updateIntervalEqual0": "You set to 0, will not update automatically.\nCan't calculate CPU status.",
|
||||
@@ -313,7 +323,7 @@
|
||||
"useCdn": "Using CDN",
|
||||
"useCdnTip": "Non-Chinese users are recommended to use CDN. Would you like to use it?",
|
||||
"useNoPwd": "No password will be used",
|
||||
"usePodmanByDefault": "Defaulting to Podman",
|
||||
"usePodmanByDefault": "Use Podman by default",
|
||||
"used": "Used",
|
||||
"user": "User",
|
||||
"versionHaveUpdate": "Found: v1.0.{build}, click to update",
|
||||
@@ -321,16 +331,17 @@
|
||||
"versionUpdated": "Current: v1.0.{build}, is up to date",
|
||||
"view": "View",
|
||||
"viewErr": "See error",
|
||||
"virtKeyHelpClipboard": "Copy to the clipboard if terminal selected is not empty, otherwise paste the contents of the clipboard to the terminal.",
|
||||
"virtKeyHelpClipboard": "Copy to the clipboard if the selected terminal is not empty, otherwise paste the content of the clipboard to the terminal.",
|
||||
"virtKeyHelpIME": "Turn on/off the keyboard",
|
||||
"virtKeyHelpSFTP": "Open current directory in SFTP.",
|
||||
"waitConnection": "Please wait for the connection to be established.",
|
||||
"wakeLock": "Keep awake",
|
||||
"watchNotPaired": "No paired Apple Watch",
|
||||
"webdavSettingEmpty": "Webdav setting is empty",
|
||||
"webdavSettingEmpty": "WebDav setting is empty",
|
||||
"whenOpenApp": "When opening the app",
|
||||
"willTakEeffectImmediately": "Will take effect immediately",
|
||||
"wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.",
|
||||
"write": "Write",
|
||||
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist."
|
||||
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
|
||||
"writeScriptTip": "After connecting to the server, a script will be written to ~/.config/server_box to monitor the system status. You can review the script content."
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"@@locale": "es",
|
||||
"about": "Acerca de",
|
||||
"aboutThanks": "Gracias a los siguientes participantes.",
|
||||
"acceptBeta": "Aceptar actualizaciones de la versión de prueba",
|
||||
"add": "Añadir",
|
||||
"addAServer": "Agregar un servidor",
|
||||
"addPrivateKey": "Agregar una llave privada",
|
||||
@@ -10,7 +11,6 @@
|
||||
"addr": "Dirección",
|
||||
"all": "Todos",
|
||||
"alreadyLastDir": "Ya estás en el directorio superior",
|
||||
"alterUrl": "URL alternativa",
|
||||
"askContinue": "{msg}, ¿continuar?",
|
||||
"attention": "Atención",
|
||||
"authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.",
|
||||
@@ -44,7 +44,6 @@
|
||||
"cnKeyboardCompTip": "Si el terminal muestra un teclado seguro, puedes activarlo.",
|
||||
"collapseUI": "Colapsar",
|
||||
"collapseUITip": "¿Colapsar por defecto las listas largas en la UI?",
|
||||
"collectUsage": "Recopilar información de uso (no relacionada con la privacidad).",
|
||||
"conn": "Conectar",
|
||||
"connected": "Conectado",
|
||||
"container": "Contenedor",
|
||||
@@ -68,7 +67,6 @@
|
||||
"decode": "Decodificar",
|
||||
"decompress": "Descomprimir",
|
||||
"delete": "Eliminar",
|
||||
"deleteScripts": "Eliminar scripts del servidor simultáneamente",
|
||||
"deleteServers": "Eliminar servidores en lote",
|
||||
"deviceName": "Nombre del dispositivo",
|
||||
"dirEmpty": "Asegúrate de que el directorio esté vacío",
|
||||
@@ -94,12 +92,15 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.",
|
||||
"encode": "Codificar",
|
||||
"envVars": "Variable de entorno",
|
||||
"error": "Error",
|
||||
"exampleName": "Ejemplo de nombre",
|
||||
"experimentalFeature": "Función experimental",
|
||||
"export": "Exportar",
|
||||
"extraArgs": "Argumentos extra",
|
||||
"failed": "Fallido",
|
||||
"fallbackSshDest": "Destino SSH alternativo",
|
||||
"fdroidReleaseTip": "Si descargaste esta aplicación desde F-Droid, se recomienda desactivar esta opción.",
|
||||
"feedback": "Retroalimentación",
|
||||
"feedbackOnGithub": "Si tienes algún problema, por favor informa en GitHub",
|
||||
"fieldMustNotEmpty": "Estos campos no pueden estar vacíos.",
|
||||
@@ -110,6 +111,7 @@
|
||||
"followSystem": "Seguir al sistema",
|
||||
"font": "Fuente",
|
||||
"fontSize": "Tamaño de fuente",
|
||||
"forExample": "Por ejemplo",
|
||||
"force": "Forzar",
|
||||
"foundNUpdate": "Encontradas {count} actualizaciones",
|
||||
"fullScreen": "Modo pantalla completa",
|
||||
@@ -133,6 +135,7 @@
|
||||
"imagesList": "Lista de imágenes",
|
||||
"import": "Importar",
|
||||
"inAppUpdate": "¿Actualizar dentro de la app? De lo contrario, descargar usando un navegador.",
|
||||
"init": "Inicializar",
|
||||
"inner": "Interno",
|
||||
"inputDomainHere": "Introduce el dominio aquí",
|
||||
"install": "Instalar",
|
||||
@@ -151,6 +154,8 @@
|
||||
"languageName": "Español",
|
||||
"lastTry": "Último intento",
|
||||
"launchPage": "Página de lanzamiento",
|
||||
"letterCache": "Caché de letras",
|
||||
"letterCacheTip": "Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.",
|
||||
"license": "Licencia de código abierto",
|
||||
"light": "Claro",
|
||||
"loadingFiles": "Cargando directorio...",
|
||||
@@ -177,6 +182,7 @@
|
||||
"noClient": "No hay conexión SSH",
|
||||
"noInterface": "No hay interfaz disponible",
|
||||
"noLineChart": "No utilice gráficos de líneas",
|
||||
"noLineChartForCpu": "No utilice gráficos lineales para la CPU",
|
||||
"noNotiPerm": "Sin permisos de notificación, posiblemente sin indicación de progreso al descargar actualizaciones de la aplicación.",
|
||||
"noOptions": "Sin opciones disponibles",
|
||||
"noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.",
|
||||
@@ -199,11 +205,11 @@
|
||||
"open": "Abrir",
|
||||
"openLastPath": "Abrir el último camino",
|
||||
"openLastPathTip": "Los diferentes servidores tendrán diferentes registros, y lo que se registra es la ruta de salida",
|
||||
"parseContainerStats": "Analizar estado de uso del contenedor",
|
||||
"parseContainerStatsTip": "El análisis del estado de uso de Docker es bastante lento",
|
||||
"paste": "Pegar",
|
||||
"path": "Ruta",
|
||||
"percentOfSize": "El {percent}% de {size}",
|
||||
"permission": "Permisos",
|
||||
"pickFile": "Seleccionar archivo",
|
||||
"pingAvg": "Promedio:",
|
||||
"pingInputIP": "Por favor, introduce la IP de destino o el dominio",
|
||||
@@ -229,6 +235,7 @@
|
||||
"rememberChoice": "Recordar la selección",
|
||||
"rememberPwdInMem": "Recordar contraseña en la memoria",
|
||||
"rememberPwdInMemTip": "Utilizado para contenedores, suspensión, etc.",
|
||||
"rememberWindowSize": "Recordar el tamaño de la ventana",
|
||||
"remotePath": "Ruta remota",
|
||||
"rename": "Renombrar",
|
||||
"reportBugsOnGithubIssue": "Por favor, informa los problemas en {url}",
|
||||
@@ -257,6 +264,7 @@
|
||||
"serverTabUnkown": "Estado desconocido",
|
||||
"setting": "Configuración",
|
||||
"sftpDlPrepare": "Preparando para conectar al servidor...",
|
||||
"sftpEditorTip": "Si está vacío, use el editor de archivos incorporado de la aplicación. Si hay un valor, use el editor del servidor remoto, por ejemplo, `vim` (se recomienda detectar automáticamente según `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios",
|
||||
"sftpSSHConnected": "SFTP conectado...",
|
||||
"sftpShowFoldersFirst": "Mostrar carpetas primero",
|
||||
@@ -271,6 +279,7 @@
|
||||
"sshTip": "Esta función está en fase de pruebas.\n\nPor favor, informa los problemas en {url}, o únete a nuestro desarrollo.",
|
||||
"sshVirtualKeyAutoOff": "Desactivación automática de teclas virtuales",
|
||||
"start": "Iniciar",
|
||||
"stat": "Estadísticas",
|
||||
"stats": "Estadísticas",
|
||||
"stop": "Detener",
|
||||
"stopped": "Detenido",
|
||||
@@ -280,6 +289,7 @@
|
||||
"suspend": "Suspender",
|
||||
"suspendTip": "La función de suspender necesita permisos de root y soporte de systemd.",
|
||||
"switchTo": "Cambiar a {val}",
|
||||
"sync": "Sincronizar",
|
||||
"syncTip": "Puede que necesites reiniciar para que algunos cambios tengan efecto.",
|
||||
"system": "Sistema",
|
||||
"tag": "Etiqueta",
|
||||
@@ -296,7 +306,7 @@
|
||||
"total": "Total",
|
||||
"traffic": "Tráfico",
|
||||
"trySudo": "Intentar con sudo",
|
||||
"ttl": "Tiempo de vida (TTL)",
|
||||
"ttl": "TTL",
|
||||
"unknown": "Desconocido",
|
||||
"unknownError": "Error desconocido",
|
||||
"unkownConvertMode": "Modo de conversión desconocido",
|
||||
@@ -332,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "Los cambios tendrán efecto inmediatamente",
|
||||
"wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.",
|
||||
"write": "Escribir",
|
||||
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe."
|
||||
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
|
||||
"writeScriptTip": "Después de conectarse al servidor, se escribirá un script en ~/.config/server_box para monitorear el estado del sistema. Puedes revisar el contenido del script."
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"@@locale": "fr",
|
||||
"about": "À propos",
|
||||
"aboutThanks": "Merci aux personnes suivantes qui ont participé.",
|
||||
"acceptBeta": "Accepter les mises à jour de la version de test",
|
||||
"add": "Ajouter",
|
||||
"addAServer": "Ajouter un serveur",
|
||||
"addPrivateKey": "Ajouter une clé privée",
|
||||
@@ -10,7 +11,6 @@
|
||||
"addr": "Adresse",
|
||||
"all": "Tous",
|
||||
"alreadyLastDir": "Déjà dans le dernier répertoire.",
|
||||
"alterUrl": "Modifier l'URL",
|
||||
"askContinue": "{msg}. Continuer ?",
|
||||
"attention": "Attention",
|
||||
"authFailTip": "Échec de l'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.",
|
||||
@@ -44,7 +44,6 @@
|
||||
"cnKeyboardCompTip": "Si le terminal affiche un clavier sécurisé, vous pouvez l'activer.",
|
||||
"collapseUI": "Réduire",
|
||||
"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",
|
||||
"connected": "Connecté",
|
||||
"container": "Conteneur",
|
||||
@@ -68,7 +67,6 @@
|
||||
"decode": "Décoder",
|
||||
"decompress": "Décompresser",
|
||||
"delete": "Supprimer",
|
||||
"deleteScripts": "Supprimer les scripts du serveur en même temps",
|
||||
"deleteServers": "Supprimer des serveurs en lot",
|
||||
"deviceName": "Nom de l'appareil",
|
||||
"dirEmpty": "Assurez-vous que le répertoire est vide.",
|
||||
@@ -94,12 +92,15 @@
|
||||
"editor": "Éditeur",
|
||||
"editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.",
|
||||
"encode": "Encoder",
|
||||
"envVars": "Variable d’environnement",
|
||||
"error": "Erreur",
|
||||
"exampleName": "Nom de l'exemple",
|
||||
"experimentalFeature": "Fonctionnalité expérimentale",
|
||||
"export": "Exporter",
|
||||
"extraArgs": "Arguments supplémentaires",
|
||||
"failed": "Échoué",
|
||||
"fallbackSshDest": "Destino SSH alternativo",
|
||||
"fdroidReleaseTip": "Si vous avez téléchargé cette application depuis F-Droid, il est recommandé de désactiver cette option.",
|
||||
"feedback": "Retour",
|
||||
"feedbackOnGithub": "Si vous avez des questions, veuillez donner votre avis sur Github.",
|
||||
"fieldMustNotEmpty": "Ces champs ne doivent pas être vides.",
|
||||
@@ -110,6 +111,7 @@
|
||||
"followSystem": "Suivre le système",
|
||||
"font": "Police",
|
||||
"fontSize": "Taille de la police",
|
||||
"forExample": "Par exemple",
|
||||
"force": "Forcer",
|
||||
"foundNUpdate": "{count} mise à jour trouvée",
|
||||
"fullScreen": "Mode plein écran",
|
||||
@@ -133,6 +135,7 @@
|
||||
"imagesList": "Liste des images",
|
||||
"import": "Importer",
|
||||
"inAppUpdate": "Mettre à jour dans l'application ? Sinon, téléchargez en utilisant un navigateur.",
|
||||
"init": "Initialiser",
|
||||
"inner": "Interne",
|
||||
"inputDomainHere": "Saisissez le domaine ici",
|
||||
"install": "Installer",
|
||||
@@ -151,6 +154,8 @@
|
||||
"languageName": "Français",
|
||||
"lastTry": "Dernière tentative",
|
||||
"launchPage": "Page de lancement",
|
||||
"letterCache": "Mise en cache des lettres",
|
||||
"letterCacheTip": "Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.",
|
||||
"license": "Licence",
|
||||
"light": "Clair",
|
||||
"loadingFiles": "Chargement des fichiers...",
|
||||
@@ -177,6 +182,7 @@
|
||||
"noClient": "Pas de client",
|
||||
"noInterface": "Pas d'interface",
|
||||
"noLineChart": "Ne pas utiliser de graphiques linéaires",
|
||||
"noLineChartForCpu": "Ne pas utiliser de graphiques linéaires pour l'unité centrale",
|
||||
"noNotiPerm": "Pas de permissions de notification, peut-être pas d'indication de progression lors de la mise à jour des applications.",
|
||||
"noOptions": "Pas d'options",
|
||||
"noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.",
|
||||
@@ -199,11 +205,11 @@
|
||||
"open": "Ouvrir",
|
||||
"openLastPath": "Ouvrir le dernier chemin",
|
||||
"openLastPathTip": "Les différents serveurs auront des journaux différents, et le journal est le chemin vers la sortie",
|
||||
"parseContainerStats": "Analyser l'état d'occupation du conteneur",
|
||||
"parseContainerStatsTip": "L'analyse de l'occupation des conteneurs Docker est relativement lente.",
|
||||
"paste": "Coller",
|
||||
"path": "Chemin",
|
||||
"percentOfSize": "{percent}% de {size}",
|
||||
"permission": "Permissions",
|
||||
"pickFile": "Choisir un fichier",
|
||||
"pingAvg": "Moy.:",
|
||||
"pingInputIP": "Veuillez saisir une adresse IP / un domaine cible.",
|
||||
@@ -229,6 +235,7 @@
|
||||
"rememberChoice": "Se souvenir du choix",
|
||||
"rememberPwdInMem": "Mémoriser le mot de passe en mémoire",
|
||||
"rememberPwdInMemTip": "Utilisé pour les conteneurs, la suspension, etc.",
|
||||
"rememberWindowSize": "Se souvenir de la taille de la fenêtre",
|
||||
"remotePath": "Chemin distant",
|
||||
"rename": "Renommer",
|
||||
"reportBugsOnGithubIssue": "Veuillez signaler les bugs sur {url}",
|
||||
@@ -257,6 +264,7 @@
|
||||
"serverTabUnkown": "État inconnu",
|
||||
"setting": "Paramètres",
|
||||
"sftpDlPrepare": "Préparation de la connexion...",
|
||||
"sftpEditorTip": "Si vide, utilisez l’éditeur de fichiers intégré de l’application. Si une valeur est présente, utilisez l’éditeur du serveur distant, par exemple `vim` (il est recommandé de détecter automatiquement selon `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.",
|
||||
"sftpSSHConnected": "SFTP Connecté",
|
||||
"sftpShowFoldersFirst": "Afficher d'abord les dossiers",
|
||||
@@ -271,6 +279,7 @@
|
||||
"sshTip": "Cette fonctionnalité est actuellement à l'étape expérimentale.\n\nVeuillez signaler les bugs sur {url} ou rejoindre notre développement.",
|
||||
"sshVirtualKeyAutoOff": "Activation automatique des touches virtuelles",
|
||||
"start": "Démarrer",
|
||||
"stat": "Statistiques",
|
||||
"stats": "Statistiques",
|
||||
"stop": "Arrêter",
|
||||
"stopped": "Arrêté",
|
||||
@@ -280,6 +289,7 @@
|
||||
"suspend": "Suspendre",
|
||||
"suspendTip": "La fonction de suspension nécessite des privilèges root et le support de systemd.",
|
||||
"switchTo": "Passer à {val}",
|
||||
"sync": "Sync",
|
||||
"syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.",
|
||||
"system": "Système",
|
||||
"tag": "Étiquettes",
|
||||
@@ -296,7 +306,7 @@
|
||||
"total": "Total",
|
||||
"traffic": "Trafic",
|
||||
"trySudo": "Essayer d'utiliser sudo",
|
||||
"ttl": "ttl",
|
||||
"ttl": "TTL",
|
||||
"unknown": "Inconnu",
|
||||
"unknownError": "Erreur inconnue",
|
||||
"unkownConvertMode": "Mode de conversion inconnu",
|
||||
@@ -332,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "Prendra effet immédiatement",
|
||||
"wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.",
|
||||
"write": "Écrire",
|
||||
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas."
|
||||
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
|
||||
"writeScriptTip": "Après la connexion au serveur, un script sera écrit dans ~/.config/server_box pour surveiller l’état du système. Vous pouvez examiner le contenu du script."
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"@@locale": "id",
|
||||
"about": "Tentang",
|
||||
"aboutThanks": "Terima kasih kepada orang -orang berikut yang berpartisipasi.",
|
||||
"acceptBeta": "Terima pembaruan versi uji coba",
|
||||
"add": "Menambahkan",
|
||||
"addAServer": "tambahkan server",
|
||||
"addPrivateKey": "Tambahkan kunci pribadi",
|
||||
@@ -10,7 +11,6 @@
|
||||
"addr": "Alamat",
|
||||
"all": "Semua",
|
||||
"alreadyLastDir": "Sudah di direktori terakhir.",
|
||||
"alterUrl": "Alter url",
|
||||
"askContinue": "{msg}, lanjutkan?",
|
||||
"attention": "Perhatian",
|
||||
"authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.",
|
||||
@@ -44,7 +44,6 @@
|
||||
"cnKeyboardCompTip": "Jika terminal munculkan keyboard aman, Anda bisa mengaktifkannya.",
|
||||
"collapseUI": "Runtuh",
|
||||
"collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak",
|
||||
"collectUsage": "Mengumpulkan informasi penggunaan (tidak terkait dengan privasi).",
|
||||
"conn": "Koneksi",
|
||||
"connected": "Terhubung",
|
||||
"container": "Wadah",
|
||||
@@ -68,7 +67,6 @@
|
||||
"decode": "Membaca sandi",
|
||||
"decompress": "Dekompresi",
|
||||
"delete": "Menghapus",
|
||||
"deleteScripts": "Menghapus skrip server secara bersamaan",
|
||||
"deleteServers": "Penghapusan server secara batch",
|
||||
"deviceName": "Nama perangkat",
|
||||
"dirEmpty": "Pastikan dir kosong.",
|
||||
@@ -94,12 +92,15 @@
|
||||
"editor": "Editor",
|
||||
"editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.",
|
||||
"encode": "Menyandi",
|
||||
"envVars": "Variabel lingkungan",
|
||||
"error": "Kesalahan",
|
||||
"exampleName": "Nama contoh",
|
||||
"experimentalFeature": "Fitur eksperimental",
|
||||
"export": "Ekspor",
|
||||
"extraArgs": "Args ekstra",
|
||||
"failed": "Gagal",
|
||||
"fallbackSshDest": "Tujuan SSH mundur",
|
||||
"fdroidReleaseTip": "Jika Anda mengunduh aplikasi ini dari F-Droid, disarankan untuk mematikan opsi ini.",
|
||||
"feedback": "Masukan",
|
||||
"feedbackOnGithub": "Jika Anda memiliki pertanyaan, silakan umpan balik tentang GitHub.",
|
||||
"fieldMustNotEmpty": "Bidang -bidang ini tidak boleh kosong.",
|
||||
@@ -110,6 +111,7 @@
|
||||
"followSystem": "Ikuti sistem",
|
||||
"font": "Font",
|
||||
"fontSize": "Ukuran huruf",
|
||||
"forExample": "Sebagai contoh",
|
||||
"force": "sukarela",
|
||||
"foundNUpdate": "Menemukan {count} pembaruan",
|
||||
"fullScreen": "Mode Layar Penuh",
|
||||
@@ -133,6 +135,7 @@
|
||||
"imagesList": "Daftar gambar",
|
||||
"import": "Impor",
|
||||
"inAppUpdate": "Perbarui di dalam aplikasi? Jika tidak, unduh menggunakan browser.",
|
||||
"init": "Menginisialisasi",
|
||||
"inner": "Batin",
|
||||
"inputDomainHere": "Input domain di sini",
|
||||
"install": "Install",
|
||||
@@ -151,6 +154,8 @@
|
||||
"languageName": "Indonesia",
|
||||
"lastTry": "Percobaan terakhir",
|
||||
"launchPage": "Halaman peluncuran",
|
||||
"letterCache": "Caching huruf",
|
||||
"letterCacheTip": "Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.",
|
||||
"license": "Lisensi",
|
||||
"light": "Terang",
|
||||
"loadingFiles": "Memuat file ...",
|
||||
@@ -171,12 +176,13 @@
|
||||
"name": "Nama",
|
||||
"needHomeDir": "Jika Anda pengguna Synology, [lihat di sini](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Pengguna sistem lain perlu mencari cara membuat direktori home.",
|
||||
"needRestart": "Perlu memulai ulang aplikasi",
|
||||
"net": "Net",
|
||||
"net": "Jaringan",
|
||||
"netViewType": "Jenis tampilan bersih",
|
||||
"newContainer": "Wadah baru",
|
||||
"noClient": "Tidak ada klien",
|
||||
"noInterface": "Tidak ada antarmuka",
|
||||
"noLineChart": "Jangan gunakan grafik garis",
|
||||
"noLineChartForCpu": "Jangan gunakan diagram garis untuk CPU",
|
||||
"noNotiPerm": "Tidak ada izin notifikasi, mungkin tidak ada indikasi kemajuan saat mengunduh pembaruan aplikasi.",
|
||||
"noOptions": "Tidak ada opsi",
|
||||
"noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.",
|
||||
@@ -199,11 +205,11 @@
|
||||
"open": "Membuka",
|
||||
"openLastPath": "Buka jalur terakhir",
|
||||
"openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar",
|
||||
"parseContainerStats": "Memecahkan status okupansi kontainer",
|
||||
"parseContainerStatsTip": "Parsing status okupansi oleh Docker agak lambat",
|
||||
"paste": "Tempel",
|
||||
"path": "Jalur",
|
||||
"percentOfSize": "{percent}% dari {size}",
|
||||
"permission": "Izin",
|
||||
"pickFile": "Pilih file",
|
||||
"pingAvg": "Rata -rata:",
|
||||
"pingInputIP": "Harap masukkan IP / domain target.",
|
||||
@@ -229,6 +235,7 @@
|
||||
"rememberChoice": "Ingat pilihan",
|
||||
"rememberPwdInMem": "Ingat kata sandi di dalam memori",
|
||||
"rememberPwdInMemTip": "Digunakan untuk kontainer, menangguhkan, dll.",
|
||||
"rememberWindowSize": "Ingat ukuran jendela",
|
||||
"remotePath": "Jalur jarak jauh",
|
||||
"rename": "Ganti nama",
|
||||
"reportBugsOnGithubIssue": "Harap laporkan bug di {url}",
|
||||
@@ -257,6 +264,7 @@
|
||||
"serverTabUnkown": "Negara yang tidak diketahui",
|
||||
"setting": "Pengaturan",
|
||||
"sftpDlPrepare": "Bersiap untuk terhubung ...",
|
||||
"sftpEditorTip": "Jika kosong, gunakan editor file bawaan aplikasi. Jika ada nilai, gunakan editor server jarak jauh, misalnya `vim` (disarankan untuk mendeteksi secara otomatis sesuai `EDITOR`).",
|
||||
"sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP",
|
||||
"sftpSSHConnected": "Sftp terhubung",
|
||||
"sftpShowFoldersFirst": "Folder ditampilkan lebih dulu",
|
||||
@@ -271,6 +279,7 @@
|
||||
"sshTip": "Fungsi ini sekarang dalam tahap eksperimen.\n\nHarap laporkan bug di {url} atau bergabunglah dengan pengembangan kami.",
|
||||
"sshVirtualKeyAutoOff": "Switching Otomatis Kunci Virtual",
|
||||
"start": "Awal",
|
||||
"stat": "Statistik",
|
||||
"stats": "Statistik",
|
||||
"stop": "Berhenti",
|
||||
"stopped": "dihentikan",
|
||||
@@ -280,6 +289,7 @@
|
||||
"suspend": "Suspend",
|
||||
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
|
||||
"switchTo": "Beralih ke {val}",
|
||||
"sync": "Sinkronisasi",
|
||||
"syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.",
|
||||
"system": "Sistem",
|
||||
"tag": "Tag",
|
||||
@@ -296,7 +306,7 @@
|
||||
"total": "Total",
|
||||
"traffic": "Lalu lintas",
|
||||
"trySudo": "Cobalah menggunakan sudo",
|
||||
"ttl": "ttl",
|
||||
"ttl": "TTL",
|
||||
"unknown": "Tidak dikenal",
|
||||
"unknownError": "Kesalahan yang tidak diketahui",
|
||||
"unkownConvertMode": "Mode Konversi Tidak Diketahui",
|
||||
@@ -332,5 +342,6 @@
|
||||
"willTakEeffectImmediately": "Akan segera berlaku",
|
||||
"wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.",
|
||||
"write": "Tulis",
|
||||
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada."
|
||||
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
|
||||
"writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke ~/.config/server_box untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut."
|
||||
}
|
||||