Compare commits

..

79 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
8e7de604ee fix: linux build 2025-06-09 18:50:01 +08:00
lollipopkit🏳️‍⚧️
6f2a58ce18 bump: v1184 2025-06-09 16:01:39 +08:00
lollipopkit🏳️‍⚧️
066629d7e0 fix: android build 2025-06-08 20:59:58 +08:00
lollipopkit🏳️‍⚧️
4b3953e0d2 readd: serverTabPreferDiskAmount (#780)
Fixes #643
2025-06-08 11:15:54 +08:00
lollipopkit🏳️‍⚧️
b5aec55106 fix android reload when physical keyboard changes (#779) 2025-06-08 10:46:47 +08:00
lollipopkit🏳️‍⚧️
ba686db847 fix: ssh terminal ui 2025-06-07 17:18:42 +08:00
lollipopkit🏳️‍⚧️
4d52023982 opt.: ssh terminal ux (#778) 2025-06-07 17:07:13 +08:00
lollipopkit🏳️‍⚧️
7a71a96442 fix: examples UI of importing (#777)
Fixes #601
2025-06-05 09:22:54 +08:00
lollipopkit🏳️‍⚧️
79c515c903 new: bio_auth -> local_auth (#776)
Fixes #722
2025-06-05 09:07:28 +08:00
lollipopkit🏳️‍⚧️
4701757857 feat: SSH page background (#775) 2025-06-05 08:53:00 +08:00
lollipopkit🏳️‍⚧️
176cb7da03 feat: disk smart info (#773) 2025-06-05 07:31:45 +08:00
lollipopkit🏳️‍⚧️
741a6442e0 fix: batch delete servers (#772) 2025-06-04 19:28:58 +08:00
lollipopkit🏳️‍⚧️
f6d394c71e opt.: custom terminal emulator (#771) 2025-06-04 19:13:31 +08:00
lollipopkit🏳️‍⚧️
7127c960f7 opt.: server detail page columns 2025-06-04 17:29:03 +08:00
lollipopkit🏳️‍⚧️
1084c49a5f opt.: ui 2025-06-04 01:52:27 +08:00
lollipopkit🏳️‍⚧️
bc824691e0 opt.: server card loading UI 2025-06-04 00:47:18 +08:00
lollipopkit🏳️‍⚧️
0c1ada0067 fix: cloud sync (#769) 2025-06-04 00:11:31 +08:00
lollipopkit🏳️‍⚧️
9547d92ac5 migrate: flutter 3.32 2025-05-25 17:05:46 +08:00
lollipopkit🏳️‍⚧️
7e16d2f159 new: parse disk info via lsblk output Fixes #709 (#760) 2025-05-17 00:45:38 +08:00
lollipopkit🏳️‍⚧️
d88e97e699 new: use generated ids for servers (#765)
* new: use generated ids for servers
Fixes #743

* fix: deps.

* fix: migrate related settings

* fix: restore servers from json
2025-05-16 21:50:44 +08:00
lollipopkit🏳️‍⚧️
d29bd1d806 opt.: ssh page sftp path checking (#763) 2025-05-15 21:01:10 +08:00
lollipopkit🏳️‍⚧️
2b2f1ddb60 opt.: handle esc btn in ssh page (#761) 2025-05-15 20:35:45 +08:00
lollipopkit🏳️‍⚧️
4f16d510c8 fix: code editor page popping (#759)
Fixes #713
2025-05-14 18:58:58 +08:00
lollipopkit🏳️‍⚧️
94cded39a6 fix: horizontal ssh virt keys ui (#758)
Fixes #737
2025-05-14 18:21:18 +08:00
lollipopkit🏳️‍⚧️
12082e1235 chore: README 2025-05-14 16:54:28 +08:00
lollipopkit🏳️‍⚧️
28e34e2183 opt.: editor lang parse 2025-05-14 16:19:26 +08:00
lollipopkit🏳️‍⚧️
4d45d01074 feat: searching in editor page (#756)
* feat: searching in editor page
Fixes #734

* opt.: editor searching ui
2025-05-14 05:09:32 +08:00
lollipopkit🏳️‍⚧️
f6b3ec2a62 opt.: editor (#755)
Fixes #753
2025-05-14 04:07:21 +08:00
lollipopkit🏳️‍⚧️
d6cf33fb70 bug: can't select file on macOS (#754)
Fixes #750
2025-05-14 04:04:19 +08:00
lollipopkit🏳️‍⚧️
1eea133b69 opt.: appbar scrolledUnderElevation (#752)
Fixes #751
2025-05-14 04:02:30 +08:00
lollipopkit🏳️‍⚧️
2b46cb6dcc fix: android monochrome icon (#749)
Fixes #732
2025-05-14 03:22:02 +08:00
lollipopkit🏳️‍⚧️
8627ff823f optimization: desktop UI (#747) 2025-05-13 04:57:37 +08:00
lollipopkit🏳️‍⚧️
e520929411 chore: migrate fl_lib 2025-04-28 23:15:54 +08:00
lollipopkit🏳️‍⚧️
8f09085cf3 Merge remote-tracking branch 'origin/lollipopkit/issue727' 2025-04-25 18:33:36 +08:00
lollipopkit🏳️‍⚧️
9e66071cb0 opt. 2025-04-25 18:32:29 +08:00
Noo6
fa90c1ef31 opt: navigation bar (#740) 2025-04-22 11:54:29 +08:00
Noo6
ede238c647 feat: adaptive navigation bar (#739) 2025-04-22 11:19:19 +08:00
lollipopkit🏳️‍⚧️
6e7fee20b8 opt.: routes 2025-04-10 15:28:47 +08:00
lollipopkit🏳️‍⚧️
391e4f6b65 opt.: page struct 2025-04-09 12:15:42 +08:00
lollipopkit🏳️‍⚧️
e185414355 fix: logo url dist null check 2025-04-08 14:49:26 +08:00
lollipopkit🏳️‍⚧️
2a2f348063 migrate: fl_lib 2025-03-24 23:07:52 +08:00
moli765
95ca6bcfc9 reslove issue 717 about logo url and add coreelec support (#718) 2025-03-22 23:19:54 +08:00
lollipopkit🏳️‍⚧️
275041247a migrate: webdav_client_plus (#729)
Fixes #723
2025-03-22 01:27:17 +08:00
lollipopkit🏳️‍⚧️
24d64b835d opt.: app bar
Fixes #727
2025-03-20 20:20:13 +08:00
lollipopkit🏳️‍⚧️
dd5fea09b1 opt.: skip updating home widget on desktop 2025-03-13 16:07:45 +08:00
𝗛𝗼𝗹𝗶
0a404e035e Improve Turkish Language (#721) 2025-03-10 15:21:17 +08:00
Noo6
b5ab5b1cab fix: window title bar might not be displayed (#701) 2025-02-12 13:33:49 +08:00
Noo6
5cb83001c6 opt: windows app icon (#700) 2025-02-12 13:17:54 +08:00
Noo6
20a39f0292 feat: record window position (#692) 2025-02-05 20:59:04 +08:00
Noo6
900686f955 chore: Fns & FnRes (#690) 2025-02-04 22:41:03 +08:00
Calvin lin
a10321e3de Update release.yml
It may work now :)
2025-02-03 16:22:18 +08:00
Calvin lin
0691ab2213 Update release.yml 2025-02-03 16:16:45 +08:00
Calvin lin
ef05203ea3 Update release.yml 2025-02-03 16:08:31 +08:00
Calvin lin
28410707a8 Update release.yml 2025-02-03 16:03:49 +08:00
Calvin lin
06b966caa8 Merge pull request #687 from lollipopkit/fix_action_linux
fix github action build linux
2025-02-03 16:01:36 +08:00
calvin
11b0806083 fix github action build linux 2025-02-03 15:48:19 +08:00
lollipopkit🏳️‍⚧️
749fd4d800 fix: ci 2025-01-29 23:56:27 +08:00
lollipopkit🏳️‍⚧️
bec4a3b314 chore: bump version 2025-01-29 23:44:44 +08:00
lollipopkit🏳️‍⚧️
9e5babec76 opt.: close after saving (#684) 2025-01-29 15:10:50 +08:00
lollipopkit🏳️‍⚧️
dbbb10364b fix: webdav settings (#683) 2025-01-29 13:13:12 +08:00
lollipopkit🏳️‍⚧️
16948c3e0f new: provide .deb & .rpm 2025-01-29 12:56:03 +08:00
lollipopkit🏳️‍⚧️
e39fb23b66 bug: unix perm switcher (#674) 2025-01-14 11:19:47 +08:00
lollipopkit🏳️‍⚧️
4777166dd9 migrate: fl_lib v235 2025-01-13 21:57:12 +08:00
Noo6
0ae0241800 opt: window title bar (#672)
* opt: window title bar

* rm: `VirtualWindowFrame` on `SettingsPage`
2025-01-10 15:19:03 +08:00
lollipopkit🏳️‍⚧️
e7a5f43cc4 fix: disabled android service related (#670)
Fixes #662
2025-01-07 20:36:12 +08:00
lollipopkit🏳️‍⚧️
7f58237589 fix: catch crash of fg service (#669) 2025-01-04 16:22:20 +08:00
lollipopkit🏳️‍⚧️
0bbd0b43b3 opt.: display settings btn in fullscreen mode (#660) 2024-12-15 23:53:31 +08:00
lollipopkit🏳️‍⚧️
aaa1eddeaf opt.: display err if home widget fails (#659) 2024-12-15 23:39:38 +08:00
lollipopkit🏳️‍⚧️
2f6db2961f fix: crash while opening terminal (#658)
Fixes #639
2024-12-14 21:06:37 +08:00
lollipopkit
831efa833b fix: file_picker err 2024-12-14 16:21:25 +08:00
lollipopkit
867fcbfc0d chore: migrate to flutter 3.27 2024-12-14 14:45:04 +08:00
lollipopkit
41886be649 opt.: home top bar 2024-12-14 14:11:26 +08:00
lollipopkit
029b4e0dba chore: README 2024-12-03 00:13:58 +08:00
lollipopkit🏳️‍⚧️
3a3c29764a bug: can't share server via qr_code (#651)
Fixes #650
2024-12-02 22:22:14 +08:00
lollipopkit
4ace4af7da opt.: home ui
- new: top left settings btn
- opt.: top logo
2024-12-02 21:41:17 +08:00
lollipopkit🏳️‍⚧️
ddd32e82d4 opt.: migrate to new fl_lib (#649)
Fixes #648
2024-12-02 21:06:44 +08:00
dsvf
b882baeafa Added Function keys (F1-F12) to SSH virtual keyboard options (#641) 2024-11-24 13:19:20 +08:00
fei1025
046f2c06d0 修复路径在windos下读取不到的问题 (#630)
* 修复路径在windos下读取不到的问题

* opt: `local.dart` fmt

---------

Co-authored-by: Noo6 <72285529+No06@users.noreply.github.com>
2024-11-14 14:42:15 +08:00
Noo6
d706886343 fix: sftp open file on windows (#633) 2024-11-14 14:24:57 +08:00
203 changed files with 23531 additions and 7537 deletions

View File

@@ -1,6 +1,7 @@
name: Flutter Release name: Flutter Release
on: on:
workflow_dispatch:
push: push:
tags: tags:
- "v*" - "v*"
@@ -18,12 +19,12 @@ jobs:
- name: Install Flutter - name: Install Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: "stable"
flutter-version: '3.24.1' flutter-version: "3.32.1"
- uses: actions/setup-java@v4 - uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: "zulu"
java-version: '17' java-version: "17"
- name: Fetch secrets - name: Fetch secrets
run: | run: |
curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key
@@ -53,14 +54,28 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Flutter - name: Install Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
- name: Install dependencies
run: |
sudo apt update
# Basic
sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev libvulkan-dev desktop-file-utils wget
# App Specific
sudo apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev
# Packaging
sudo wget https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage -O /bin/appimagetool
sudo chmod +x /bin/appimagetool
- name: Build - name: Build
run: | run: |
dart run fl_build -p linux dart run fl_build -p linux
- name: Rename artifacts
run: |
appimage_name=$(ls dist/*/*.AppImage)
mv $appimage_name ${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.appimage
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: | files: |
${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.AppImage ${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.appimage
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -90,9 +105,9 @@ jobs:
# uses: actions/checkout@v4 # uses: actions/checkout@v4
# - name: Install Flutter # - name: Install Flutter
# uses: subosito/flutter-action@v2 # uses: subosito/flutter-action@v2
# with: # with:
# channel: 'stable' # channel: 'stable'
# flutter-version: '3.22.2' # flutter-version: '3.32.1'
# - name: Build # - name: Build
# run: dart run fl_build -p ios,mac # run: dart run fl_build -p ios,mac
# - name: Create Release # - name: Create Release

2
.gitignore vendored
View File

@@ -46,6 +46,7 @@ app.*.map.json
/android/app/release /android/app/release
/android/app/fjy.androidstudio.key /android/app/fjy.androidstudio.key
/android/app/app.key
/release /release
test.dart test.dart
@@ -64,3 +65,4 @@ untranlated.json
.vscode/settings.json .vscode/settings.json
more_build_data.json more_build_data.json
trans.txt trans.txt
android/app/.cxx

View File

@@ -6,16 +6,17 @@ English | [简体中文](README_zh.md)
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a> <a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a>
<img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan"> <img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-yellow"> <img alt="license" src="https://img.shields.io/badge/license-GPLv3-yellow">
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</div> </div>
<p align="center"> <p align="center">
A Flutter project which provide charts to display <a href="../../issues/43">Linux</a> server status and tools to manage server. A Flutter project which provide charts to display <a href="https://github.com/lollipopkit/flutter_server_box/issues/43">Linux</a> server status and tools to manage server.
<br> <br>
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>. Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
</p> </p>
## 🏙️ Screenshots ## 🏙️ Screenshots
<table> <table>
<tr> <tr>
<td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/1.jpg"></td> <td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/1.jpg"></td>
@@ -25,24 +26,22 @@ Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartss
</tr> </tr>
</table> </table>
## 📥 Install ## 📥 Install
Platform | From | Platform | From |
--- | --- |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703) | 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/) | Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?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) | Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) |
Please only download pkgs from the source that **you trust**! Please only download pkgs from the source that **you trust**!
## 🔖 Feature ## 🔖 Feature
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process & Systemd`... - `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process & Systemd`...
- Platform specific: `Bio auth``Msg push``Home widget``watchOS App`... - Platform specific: `Bio auth``Msg push``Home widget``watchOS App`...
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic), Türkçe [@mikropsoft](https://github.com/mikropsoft), Українська мова [@CakesTwix](https://github.com/CakesTwix); Español, Русский язык, Português, 日本語 (Generated by GPT) - English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic), Türkçe [@mikropsoft](https://github.com/mikropsoft), Українська мова [@CakesTwix](https://github.com/CakesTwix); Español, Русский язык, Português, 日本語 (Generated by GPT)
## 🆘 Help ## 🆘 Help
<div align="center"> <div align="center">
@@ -54,30 +53,33 @@ Please only download pkgs from the source that **you trust**!
- **Common issues** can be found in [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki). - **Common issues** can be found in [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki).
Before you open an issue, please read the following: Before you open an issue, please read the following:
1. Paste the **entire log** (click the top right of the home page) in the issue template. 1. Paste the **entire log** (click the top right of the home page) in the issue template.
2. Make sure whether the issue is caused by ServerBox app. 2. Make sure whether the issue is caused by ServerBox app.
3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted. 3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted.
After you read the above, you can 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 ## 🧱 Contribution
Any positive contribution is welcome. Any positive contribution is welcome.
### Development ### Development
1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment. 1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment.
2. Clone this repo, run `flutter run` to start the app. 2. Clone this repo, run `flutter run` to start the app.
3. Run `dart run fl_build -p PLATFORM` to build the app. 3. Run `dart run fl_build -p PLATFORM` to build the app.
### Translation ### Translation
- [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog. - [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog.
- We need your help! Just feel free to open a PR. - We need your help! Just feel free to open a PR.
## 💡 My other apps ## 💡 My other apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms. - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms.
- [More](https://github.com/lollipopkit) - Tools & etc. - [More](https://github.com/lollipopkit) - Tools & etc.
## 📝 License ## 📝 License
`GPL v3 lollipopkit` `GPL v3 lollipopkit`

View File

@@ -6,10 +6,11 @@
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a> <a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a>
<img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan"> <img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan">
<img alt="license" src="https://img.shields.io/badge/证书-GPLv3-yellow"> <img alt="license" src="https://img.shields.io/badge/证书-GPLv3-yellow">
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</div> </div>
<p align="center"> <p align="center">
使用 Flutter 开发的 <a href="../../issues/43">Linux</a> 服务器工具箱,提供服务器状态图表和管理工具。 使用 Flutter 开发的 <a href="https://github.com/lollipopkit/flutter_server_box/issues/43">Linux</a> 服务器工具箱,提供服务器状态图表和管理工具。
<br> <br>
特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a> 特别感谢 <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>
</p> </p>
@@ -28,11 +29,11 @@
## 📥 安装 ## 📥 安装
平台 | 下载 平台 | 下载
--- | --- ----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703) 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/) Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?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) Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)
请从 **信任** 的来源下载! 请从 **信任** 的来源下载!
@@ -72,7 +73,7 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
3. 运行 `dart run fl_build -p PLATFORM` 构建应用 3. 运行 `dart run fl_build -p PLATFORM` 构建应用
### 翻译 ### 翻译
[指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。 [指南](https://blog.lpkt.cn/faq/) 可在我的博客中找到。
## 💡 我的其它 Apps ## 💡 我的其它 Apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。 - [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。

View File

@@ -11,11 +11,13 @@ include: package:flutter_lints/flutter.yaml
analyzer: analyzer:
exclude: exclude:
- '**/*.g.dart' - "**/*.g.dart"
language: language:
# strict-casts: true # strict-casts: true
# strict-inference: true # strict-inference: true
# strict-raw-types: true # strict-raw-types: true
errors:
invalid_annotation_target: ignore
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
@@ -41,8 +43,9 @@ linter:
annotate_overrides: true annotate_overrides: true
avoid_empty_else: true avoid_empty_else: true
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
avoid_return_types_on_setters: true avoid_return_types_on_setters: true
directives_ordering: true # Enable sorting of imports
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View File

@@ -85,11 +85,11 @@ android {
} }
debug { debug {
applicationIdSuffix '.debug' // No applicationIdSuffix or resValue here
} }
profile { profile {
applicationIdSuffix '.debug' // No applicationIdSuffix or resValue here
} }
} }
} }

View File

@@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:label="ServerBox" android:label="@string/app_name"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="true" android:allowBackup="true"
@@ -23,7 +23,7 @@
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|layoutDirection|fontScale|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as

View File

@@ -4,85 +4,158 @@ import android.app.*
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log
import java.io.File
import java.util.*
class ForegroundService : Service() { class ForegroundService : Service() {
private val chanId = "ForegroundServiceChannel" private val chanId = "ForegroundServiceChannel"
private fun logError(message: String, error: Throwable? = null) {
Log.e("ForegroundService", message, error)
try {
val logFile = File(getExternalFilesDir(null), "server_box.log")
val timestamp = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(Date())
val logMessage = "$timestamp [ForegroundService] ERROR: $message\n${error?.stackTraceToString() ?: ""}\n"
logFile.appendText(logMessage)
} catch (e: Exception) {
Log.e("ForegroundService", "Failed to write log", e)
}
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.d("ForegroundService", "Service onCreate")
createNotificationChannel() createNotificationChannel()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) { try {
"ACTION_STOP_FOREGROUND" -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
androidx.core.content.ContextCompat.checkSelfPermission(
this, android.Manifest.permission.POST_NOTIFICATIONS
) != android.content.pm.PackageManager.PERMISSION_GRANTED
) {
Log.w("ForegroundService", "Notification permission denied. Stopping service.")
stopForegroundService() stopForegroundService()
return START_NOT_STICKY return START_NOT_STICKY
} }
else -> {
val notification = createNotification() if (intent == null) {
startForeground(1, notification) Log.w("ForegroundService", "onStartCommand called with null intent")
return START_STICKY stopForegroundService()
return START_NOT_STICKY
} }
val action = intent.action
Log.d("ForegroundService", "onStartCommand action=$action")
// Create notification before starting foreground
val notification = createNotification()
// Use try-catch for startForeground
try {
startForeground(1, notification)
} catch (e: Exception) {
logError("Failed to start foreground", e)
stopSelf()
return START_NOT_STICKY
}
return when (action) {
"ACTION_STOP_FOREGROUND" -> {
stopForegroundService()
START_NOT_STICKY
}
else -> {
START_STICKY
}
}
} catch (e: Exception) {
logError("Error in onStartCommand", e)
stopSelf()
return START_NOT_STICKY
} }
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
} }
private fun createNotificationChannel() { private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java)
if (manager == null) {
Log.e("ForegroundService", "Failed to get NotificationManager")
return
}
val serviceChannel = NotificationChannel( val serviceChannel = NotificationChannel(
chanId, chanId,
chanId, "ForegroundServiceChannel",
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
) ).apply {
val manager = getSystemService(NotificationManager::class.java) description = "For foreground service"
}
manager.createNotificationChannel(serviceChannel) manager.createNotificationChannel(serviceChannel)
} }
} }
private fun createNotification(): Notification { private fun createNotification(): Notification {
val notificationIntent = Intent(this, MainActivity::class.java) try {
val pendingIntent = PendingIntent.getActivity( val notificationIntent = Intent(this, MainActivity::class.java)
this, val pendingIntent = PendingIntent.getActivity(
0, this,
notificationIntent, 0,
PendingIntent.FLAG_IMMUTABLE notificationIntent,
) PendingIntent.FLAG_IMMUTABLE
)
val deleteIntent = Intent(this, ForegroundService::class.java).apply { val deleteIntent = Intent(this, ForegroundService::class.java).apply {
action = "ACTION_STOP_FOREGROUND" action = "ACTION_STOP_FOREGROUND"
} }
val deletePendingIntent = PendingIntent.getService( val deletePendingIntent = PendingIntent.getService(
this, this,
0, 0,
deleteIntent, deleteIntent,
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
) )
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, chanId) Notification.Builder(this, chanId)
} else {
Notification.Builder(this)
}
return builder
.setContentTitle("Server Box") .setContentTitle("Server Box")
.setContentText("Open the app") .setContentText("Running in background")
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent) .addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent)
.build() .build()
} else { } catch (e: Exception) {
Notification.Builder(this) logError("Error creating notification", e)
// Return a basic notification as fallback
return Notification.Builder(this)
.setContentTitle("Server Box") .setContentTitle("Server Box")
.setContentText("Open the app")
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent)
.build() .build()
} }
} }
fun stopForegroundService() { private fun stopForegroundService() {
stopForeground(true) try {
stopForeground(true)
} catch (e: Exception) {
logError("Error stopping foreground", e)
}
stopSelf() stopSelf()
Log.d("ForegroundService", "ForegroundService stopped")
}
override fun onDestroy() {
super.onDestroy()
Log.d("ForegroundService", "Service onDestroy")
} }
} }

View File

@@ -9,13 +9,15 @@ import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import android.appwidget.AppWidgetManager
import tech.lolli.toolbox.widget.HomeWidget
class MainActivity: FlutterFragmentActivity() { class MainActivity: FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger
MethodChannel(binaryMessenger, "tech.lolli.toolbox/app_retain").apply { MethodChannel(binaryMessenger, "tech.lolli.toolbox/main_chan").apply {
setMethodCallHandler { method, result -> setMethodCallHandler { method, result ->
when (method.method) { when (method.method) {
"sendToBackground" -> { "sendToBackground" -> {
@@ -23,12 +25,19 @@ class MainActivity: FlutterFragmentActivity() {
result.success(null) result.success(null)
} }
"startService" -> { "startService" -> {
reqPerm() try {
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java) reqPerm()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
startForegroundService(serviceIntent) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { startForegroundService(serviceIntent)
startService(serviceIntent) } else {
startService(serviceIntent)
}
result.success(null)
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to start service: ${e.message}")
result.error("SERVICE_ERROR", e.message, null)
} }
} }
"stopService" -> { "stopService" -> {
@@ -36,6 +45,12 @@ class MainActivity: FlutterFragmentActivity() {
stopService(serviceIntent) stopService(serviceIntent)
result.success(null) result.success(null)
} }
"updateHomeWidget" -> {
val intent = Intent(this@MainActivity, HomeWidget::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
sendBroadcast(intent)
result.success(null)
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@@ -46,13 +61,21 @@ class MainActivity: FlutterFragmentActivity() {
private fun reqPerm() { private fun reqPerm() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
// Check if we already have the permission to avoid unnecessary prompts
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions( try {
this, ActivityCompat.requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS), this,
123, arrayOf(Manifest.permission.POST_NOTIFICATIONS),
) 123,
)
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
}
} }
} }
} }

View File

@@ -6,15 +6,18 @@ import android.appwidget.AppWidgetProvider
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject import org.json.JSONObject
import tech.lolli.toolbox.R import tech.lolli.toolbox.R
import java.net.URL import java.net.URL
import java.net.HttpURLConnection
import java.io.FileNotFoundException
class HomeWidget : AppWidgetProvider() { class HomeWidget : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
@@ -23,7 +26,6 @@ class HomeWidget : AppWidgetProvider() {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
val views = RemoteViews(context.packageName, R.layout.home_widget) val views = RemoteViews(context.packageName, R.layout.home_widget)
val sp = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) val sp = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
@@ -36,6 +38,10 @@ class HomeWidget : AppWidgetProvider() {
url = gUrl url = gUrl
} }
if (url.isNullOrEmpty()) {
Log.e("HomeWidget", "URL not found")
}
val intentUpdate = Intent(context, HomeWidget::class.java) val intentUpdate = Intent(context, HomeWidget::class.java)
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val ids = intArrayOf(appWidgetId) val ids = intArrayOf(appWidgetId)
@@ -54,11 +60,13 @@ class HomeWidget : AppWidgetProvider() {
views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate) views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate)
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE) views.setTextViewText(R.id.widget_name, "No URL")
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE) // Update the widget to display a message for missing URL
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE) views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE) views.setTextViewText(R.id.error_message, "Please configure the widget URL.")
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId") views.setViewVisibility(R.id.widget_content, View.GONE)
views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.error_message, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views) appWidgetManager.updateAppWidget(appWidgetId, views)
return return
} else { } else {
@@ -68,44 +76,53 @@ class HomeWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.widget_net_label, View.VISIBLE) views.setViewVisibility(R.id.widget_net_label, View.VISIBLE)
} }
GlobalScope.launch(Dispatchers.IO) { CoroutineScope(Dispatchers.IO).launch {
try { try {
val jsonStr = URL(url).readText() val connection = URL(url).openConnection() as HttpURLConnection
val jsonObject = JSONObject(jsonStr) connection.requestMethod = "GET"
val data = jsonObject.getJSONObject("data") val responseCode = connection.responseCode
val server = data.getString("name") if (responseCode == HttpURLConnection.HTTP_OK) {
val cpu = data.getString("cpu") val jsonStr = connection.inputStream.bufferedReader().use { it.readText() }
val mem = data.getString("mem") val jsonObject = JSONObject(jsonStr)
val disk = data.getString("disk") val data = jsonObject.getJSONObject("data")
val net = data.getString("net") val server = data.getString("name")
val cpu = data.getString("cpu")
GlobalScope.launch(Dispatchers.Main) main@ { val mem = data.getString("mem")
// mem or disk is empty -> get status failed val disk = data.getString("disk")
// (cpu | net) isEmpty -> data is not ready val net = data.getString("net")
if (mem.isEmpty() || disk.isEmpty()) { withContext(Dispatchers.Main) {
return@main if (mem.isEmpty() || disk.isEmpty()) {
Log.e("HomeWidget", "Failed to retrieve status: Memory or disk information is empty")
return@withContext
}
views.setTextViewText(R.id.widget_name, server)
views.setTextViewText(R.id.widget_cpu, cpu)
views.setTextViewText(R.id.widget_mem, mem)
views.setTextViewText(R.id.widget_disk, disk)
views.setTextViewText(R.id.widget_net, net)
val timeStr = android.text.format.DateFormat.format("HH:mm", java.util.Date()).toString()
views.setTextViewText(R.id.widget_time, timeStr)
views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.widget_cpu_label, "setAlpha", 1f)
views.setFloat(R.id.widget_mem_label, "setAlpha", 1f)
views.setFloat(R.id.widget_disk_label, "setAlpha", 1f)
views.setFloat(R.id.widget_net_label, "setAlpha", 1f)
views.setFloat(R.id.widget_time, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views)
} }
views.setTextViewText(R.id.widget_name, server) } else {
throw FileNotFoundException("HTTP response code: $responseCode")
views.setTextViewText(R.id.widget_cpu, cpu)
views.setTextViewText(R.id.widget_mem, mem)
views.setTextViewText(R.id.widget_disk, disk)
views.setTextViewText(R.id.widget_net, net)
val timeStr = android.text.format.DateFormat.format("HH:mm", java.util.Date()).toString()
views.setTextViewText(R.id.widget_time, timeStr)
appWidgetManager.updateAppWidget(appWidgetId, views)
} }
} catch (e: Exception) { } catch (e: Exception) {
println("ServerBoxHomeWidget: ${e.localizedMessage}") Log.e("HomeWidget", "Error updating widget: ${e.localizedMessage}", e)
GlobalScope.launch(Dispatchers.Main) main@ { withContext(Dispatchers.Main) {
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE) views.setTextViewText(R.id.widget_name, "Error")
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE) // Update the widget to display a message for data retrieval failure
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE) views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE) views.setTextViewText(R.id.error_message, "Failed to retrieve data.")
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId") views.setViewVisibility(R.id.widget_content, View.GONE)
views.setTextViewText(R.id.widget_mem, e.localizedMessage) views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.error_message, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views) appWidgetManager.updateAppWidget(appWidgetId, views)
} }
} }

View File

@@ -16,124 +16,151 @@
android:textSize="23sp" android:textSize="23sp"
android:textStyle="bold" android:textStyle="bold"
android:maxLines="1" android:maxLines="1"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="Server Name" /> tools:text="Server Name" />
<RelativeLayout <!-- Wrap the content in a LinearLayout for easy visibility management -->
android:id="@+id/widget_container_inner" <LinearLayout
android:id="@+id/widget_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:gravity="center_vertical" android:orientation="vertical"
android:layout_below="@id/widget_name"
android:paddingTop="13dp"> android:paddingTop="13dp">
<LinearLayout <RelativeLayout
android:id="@+id/widget_cpu_label" android:id="@+id/widget_container_inner"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:paddingBottom="2.7dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:paddingTop="13dp"
android:animateLayoutChanges="true">
<ImageView <LinearLayout
android:layout_width="17dp" android:id="@+id/widget_cpu_label"
android:layout_height="17dp" android:layout_width="wrap_content"
android:src="@drawable/speed_24">
</ImageView>
<TextView
android:id="@+id/widget_cpu"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="11dp" android:paddingBottom="2.7dp"
android:singleLine="true" android:gravity="center_vertical"
android:ellipsize = "marquee" android:orientation="horizontal">
android:textColor="@color/widgetSummaryText"
android:textSize="12.7sp" <ImageView
tools:text="CPU" /> android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/speed_24">
</ImageView>
<TextView
android:id="@+id/widget_cpu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:singleLine="true"
android:ellipsize = "marquee"
android:textColor="@color/widgetSummaryText"
android:textSize="12.7sp"
tools:text="CPU" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/widget_mem_label" android:id="@+id/widget_mem_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="2.7dp"
android:layout_below="@id/widget_cpu_label"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/memory_24">
</ImageView>
<TextView
android:id="@+id/widget_mem"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="11dp" android:paddingBottom="2.7dp"
android:maxLines="1" android:layout_below="@id/widget_cpu_label"
android:textColor="@color/widgetSummaryText" android:gravity="center_vertical"
android:textSize="12.7sp" android:orientation="horizontal">
tools:text="Mem" />
</LinearLayout> <ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/memory_24">
</ImageView>
<LinearLayout <TextView
android:id="@+id/widget_disk_label" android:id="@+id/widget_mem"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="2.7dp" android:layout_marginStart="11dp"
android:layout_below="@id/widget_mem_label" android:maxLines="1"
android:gravity="center_vertical" android:textColor="@color/widgetSummaryText"
android:orientation="horizontal"> android:textSize="12.7sp"
tools:text="Mem" />
<ImageView </LinearLayout>
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/storage_24">
</ImageView>
<TextView <LinearLayout
android:id="@+id/widget_disk" android:id="@+id/widget_disk_label"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="11dp" android:paddingBottom="2.7dp"
android:maxLines="1" android:layout_below="@id/widget_mem_label"
android:textColor="@color/widgetSummaryText" android:gravity="center_vertical"
android:textSize="12.7sp" android:orientation="horizontal">
tools:text="Disk" />
</LinearLayout> <ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/storage_24">
</ImageView>
<LinearLayout <TextView
android:id="@+id/widget_net_label" android:id="@+id/widget_disk"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/widget_disk_label" android:layout_marginStart="11dp"
android:gravity="center_vertical" android:maxLines="1"
android:orientation="horizontal"> android:textColor="@color/widgetSummaryText"
android:textSize="12.7sp"
tools:text="Disk" />
<ImageView </LinearLayout>
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/net_24">
</ImageView>
<TextView <LinearLayout
android:id="@+id/widget_net" android:id="@+id/widget_net_label"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="11dp" android:layout_below="@id/widget_disk_label"
android:maxLines="1" android:gravity="center_vertical"
android:textColor="@color/widgetSummaryText" android:orientation="horizontal">
android:textSize="12.7sp"
tools:text="Net" />
</LinearLayout> <ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/net_24">
</ImageView>
</RelativeLayout> <TextView
android:id="@+id/widget_net"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:maxLines="1"
android:textColor="@color/widgetSummaryText"
android:textSize="12.7sp"
tools:text="Net" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
<!-- Add a TextView for error messages -->
<TextView
android:id="@+id/error_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/widget_name"
android:textColor="@color/widgetSummaryText"
android:textSize="12sp"
android:visibility="gone"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="Error message" />
<TextView <TextView
android:id="@+id/widget_time" android:id="@+id/widget_time"
@@ -143,6 +170,8 @@
android:maxLines="2" android:maxLines="2"
android:textColor="@color/widgetSummaryText" android:textColor="@color/widgetSummaryText"
android:textSize="11sp" android:textSize="11sp"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="UpdateTime" /> tools:text="UpdateTime" />
</RelativeLayout> </RelativeLayout>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ServerBox</string>
</resources>

View File

@@ -9,6 +9,23 @@ rootProject.buildDir = '../build'
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
} }
subprojects { subproject ->
// Only works on com.android.application(the main app module)
if (subproject.plugins.hasPlugin('com.android.application')) {
subproject.afterEvaluate {
android.buildTypes.matching { it.name == 'profile' }.all { buildType ->
buildType.applicationIdSuffix = ".profile"
buildTypes.profile.resValue 'string', 'app_name', 'SrvBxP'
}
android.buildTypes.matching { it.name == 'debug' }.all { buildType ->
buildType.applicationIdSuffix = ".debug"
buildTypes.debug.resValue 'string', 'app_name', 'SrvBxD'
}
}
}
}
subprojects { subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }

View File

@@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

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

View File

@@ -19,8 +19,8 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.4.2" apply false id "com.android.application" version '8.6.0' apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false id "org.jetbrains.kotlin.android" version "2.1.21" apply false
} }
include ":app" include ":app"

13
distribute_options.yaml Normal file
View File

@@ -0,0 +1,13 @@
variables:
output: dist/
releases:
- name: linux
jobs:
- name: release-linux-deb
package:
platform: linux
target: deb
- name: release-linux-rpm
package:
platform: linux
target: rpm

View File

@@ -6,7 +6,7 @@ PODS:
- file_picker (0.0.1): - file_picker (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_native_splash (0.0.1): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- icloud_storage (0.0.1): - icloud_storage (0.0.1):
- Flutter - Flutter
@@ -31,8 +31,6 @@ PODS:
- Flutter - Flutter
- watch_connectivity (0.0.1): - watch_connectivity (0.0.1):
- Flutter - Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`) - app_links (from `.symlinks/plugins/app_links/ios`)
@@ -50,7 +48,6 @@ DEPENDENCIES:
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`) - watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: app_links:
@@ -83,27 +80,24 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios" :path: ".symlinks/plugins/wakelock_plus/ios"
watch_connectivity: watch_connectivity:
:path: ".symlinks/plugins/watch_connectivity/ios" :path: ".symlinks/plugins/watch_connectivity/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287 file_picker: fb04e739ae6239a76ce1f571863a196a922c87d4
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1 plain_notification_token: 047876b9d80a5b93565ddcc13a487a7e7b906f7d
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82 watch_connectivity: 88e5bea25b473e66ef8d3f960954d154ed0356d6
webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1
PODFILE CHECKSUM: ec6ef69056f066e8b21a3391082f23b5ad2d37f8 PODFILE CHECKSUM: ec6ef69056f066e8b21a3391082f23b5ad2d37f8
COCOAPODS: 1.15.2 COCOAPODS: 1.16.2

View File

@@ -672,7 +672,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -682,7 +682,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -808,7 +808,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -818,7 +818,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -836,7 +836,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -846,7 +846,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -867,7 +867,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -880,7 +880,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -906,7 +906,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -919,7 +919,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -942,7 +942,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -955,7 +955,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -978,7 +978,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -990,7 +990,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1019,7 +1019,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1031,7 +1031,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;
@@ -1057,7 +1057,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1104; CURRENT_PROJECT_VERSION = 1185;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1069,7 +1069,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1104; MARKETING_VERSION = 1.0.1185;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;

View File

@@ -26,6 +26,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@@ -43,11 +44,13 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">

View File

@@ -1,4 +1,6 @@
arb-dir: lib/l10n arb-dir: lib/l10n
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-localization-file: l10n.dart output-localization-file: l10n.dart
output-dir: lib/generated/l10n
synthetic-package: false
untranslated-messages-file: untranlated.json untranslated-messages-file: untranlated.json

View File

@@ -1,14 +1,14 @@
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart'; import 'package:fl_lib/generated/l10n/lib_l10n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/rebuild.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/home/home.dart'; import 'package:server_box/generated/l10n/l10n.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:server_box/view/page/home.dart';
part 'intro.dart'; part 'intro.dart';
@@ -22,48 +22,67 @@ class MyApp extends StatelessWidget {
listenable: RNodes.app, listenable: RNodes.app,
builder: (context, _) { builder: (context, _) {
if (!Stores.setting.useSystemPrimaryColor.fetch()) { if (!Stores.setting.useSystemPrimaryColor.fetch()) {
final colorSeed = Color(Stores.setting.colorSeed.fetch()); return _build(context);
UIs.colorSeed = colorSeed;
// Past code uses [UIs.primaryColor] as the primary color
UIs.primaryColor = colorSeed;
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) { return _buildDynamicColor(context);
final lightTheme = ThemeData(
useMaterial3: true,
colorScheme: light,
);
final darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: dark,
);
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);
},
);
}, },
); );
} }
Widget _buildApp(BuildContext ctx, Widget _build(BuildContext context) {
{required ThemeData light, required ThemeData dark}) { final colorSeed = Color(Stores.setting.colorSeed.fetch());
UIs.colorSeed = colorSeed;
UIs.primaryColor = colorSeed;
return _buildApp(
context,
light: ThemeData(
useMaterial3: true,
colorSchemeSeed: UIs.colorSeed,
appBarTheme: AppBarTheme(
scrolledUnderElevation: 0.0,
),
),
dark: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: UIs.colorSeed,
appBarTheme: AppBarTheme(
scrolledUnderElevation: 0.0,
),
),
);
}
Widget _buildDynamicColor(BuildContext context) {
return DynamicColorBuilder(
builder: (light, dark) {
final lightTheme = ThemeData(
useMaterial3: true,
colorScheme: light,
);
final darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: dark,
);
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);
},
);
}
Widget _buildApp(
BuildContext ctx, {
required ThemeData light,
required ThemeData dark,
}) {
final tMode = Stores.setting.themeMode.fetch(); final tMode = Stores.setting.themeMode.fetch();
// Issue #57 // Issue #57
final themeMode = switch (tMode) { final themeMode = switch (tMode) {
@@ -75,6 +94,14 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
key: ValueKey(locale), key: ValueKey(locale),
builder: (context, child) => ResponsiveBreakpoints.builder(
child: child ?? UIs.placeholder,
breakpoints: const [
Breakpoint(start: 0, end: 450, name: MOBILE),
Breakpoint(start: 451, end: 800, name: TABLET),
Breakpoint(start: 801, end: 1920, name: DESKTOP),
],
),
locale: locale, locale: locale,
localizationsDelegates: const [ localizationsDelegates: const [
LibLocalizations.delegate, LibLocalizations.delegate,
@@ -87,21 +114,25 @@ class MyApp extends StatelessWidget {
themeMode: themeMode, themeMode: themeMode,
theme: light.fixWindowsFont, theme: light.fixWindowsFont,
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont, darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
home: VirtualWindowFrame( home: Builder(
child: Builder( builder: (context) {
builder: (context) { context.setLibL10n();
context.setLibL10n(); final appL10n = AppLocalizations.of(context);
final appL10n = AppLocalizations.of(context); if (appL10n != null) l10n = appL10n;
if (appL10n != null) l10n = appL10n;
final intros = _IntroPage.builders; Widget child;
if (intros.isNotEmpty) { final intros = _IntroPage.builders;
return _IntroPage(intros); if (intros.isNotEmpty) {
} child = _IntroPage(intros);
}
return const HomePage(); child = const HomePage();
},
), return VirtualWindowFrame(
title: BuildData.name,
child: child,
);
},
), ),
); );
} }

30
lib/core/chan.dart Normal file
View File

@@ -0,0 +1,30 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/services.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart';
abstract final class MethodChans {
static const _channel = MethodChannel('${Miscs.pkgName}/main_chan');
static void moveToBg() {
_channel.invokeMethod('sendToBackground');
}
/// Issue #662
static void startService() {
// if (Stores.setting.fgService.fetch() != true) return;
// _channel.invokeMethod('startService');
}
/// Issue #662
static void stopService() {
// if (Stores.setting.fgService.fetch() != true) return;
// _channel.invokeMethod('stopService');
}
static void updateHomeWidget() async {
if (!isIOS || !isAndroid) return;
if (!Stores.setting.autoUpdateHomeWidget.fetch()) return;
await _channel.invokeMethod('updateHomeWidget');
}
}

View File

@@ -1,18 +0,0 @@
import 'package:flutter/services.dart';
import 'package:server_box/data/res/misc.dart';
abstract final class BgRunMC {
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain');
static void moveToBg() {
_channel.invokeMethod('sendToBackground');
}
static void startService() {
_channel.invokeMethod('startService');
}
static void stopService() {
_channel.invokeMethod('stopService');
}
}

View File

@@ -1,12 +0,0 @@
import 'package:flutter/services.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');
static void update() {
if (!Stores.setting.autoUpdateHomeWidget.fetch()) return;
_channel.invokeMethod('update');
}
}

View File

@@ -1,4 +1,9 @@
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/l10n_en.dart'; import 'package:server_box/generated/l10n/l10n.dart';
import 'package:server_box/generated/l10n/l10n_en.dart';
AppLocalizations l10n = AppLocalizationsEn(); AppLocalizations l10n = AppLocalizationsEn();
extension LocaleX on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this)!;
}

View File

@@ -12,17 +12,17 @@ extension SftpFileX on SftpFileMode {
UnixPerm toUnixPerm() { UnixPerm toUnixPerm() {
return UnixPerm( return UnixPerm(
user: RWX( user: UnixPermOp(
r: userRead, r: userRead,
w: userWrite, w: userWrite,
x: userExecute, x: userExecute,
), ),
group: RWX( group: UnixPermOp(
r: groupRead, r: groupRead,
w: groupWrite, w: groupWrite,
x: groupExecute, x: groupExecute,
), ),
other: RWX( other: UnixPermOp(
r: otherRead, r: otherRead,
w: otherWrite, w: otherWrite,
x: otherExecute, x: otherExecute,

View File

@@ -1,169 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/server_private_info.dart';
import 'package:server_box/data/res/store.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/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/data/model/server/snippet.dart';
import 'package:server_box/view/page/process.dart';
import 'package:server_box/view/page/server/tab.dart';
import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart';
import 'package:server_box/view/page/setting/seq/srv_seq.dart';
import 'package:server_box/view/page/snippet/edit.dart';
import 'package:server_box/view/page/storage/sftp.dart';
import 'package:server_box/view/page/storage/sftp_mission.dart';
class AppRoutes { /// The args class for [AppRoute].
final Widget page; final class SpiRequiredArgs {
final String title; /// The only required argument for this class.
final Spi spi;
AppRoutes(this.page, this.title); const SpiRequiredArgs(this.spi);
Future<T?> go<T>(BuildContext context) {
return Navigator.push<T>(
context,
Stores.setting.cupertinoRoute.fetch()
? CupertinoPageRoute(builder: (context) => page)
: MaterialPageRoute(builder: (context) => page),
);
}
Future<T?> checkGo<T>({
required BuildContext context,
required bool Function() check,
}) {
if (check()) {
return go(context);
}
return Future.value(null);
}
static AppRoutes serverDetail({Key? key, required Spi spi}) {
return AppRoutes(ServerDetailPage(key: key, spi: spi), 'server_detail');
}
static AppRoutes serverTab({Key? key}) {
return AppRoutes(ServerPage(key: key), 'server_tab');
}
static AppRoutes keyEdit({Key? key, PrivateKeyInfo? pki}) {
return AppRoutes(
PrivateKeyEditPage(pki: pki),
'key_${pki == null ? 'add' : 'edit'}',
);
}
static AppRoutes snippetEdit({Key? key, Snippet? snippet}) {
return AppRoutes(
SnippetEditPage(snippet: snippet),
'snippet_${snippet == null ? 'add' : 'edit'}',
);
}
static AppRoutes ssh({
Key? key,
required Spi spi,
String? initCmd,
Snippet? initSnippet,
}) {
return AppRoutes(
SSHPage(
key: key,
spi: spi,
initCmd: initCmd,
initSnippet: initSnippet,
),
'ssh_term',
);
}
static AppRoutes sshVirtKeySetting({Key? key}) {
return AppRoutes(SSHVirtKeySettingPage(key: key), 'ssh_virt_key_setting');
}
static AppRoutes sftpMission({Key? key}) {
return AppRoutes(SftpMissionPage(key: key), 'sftp_mission');
}
static AppRoutes sftp(
{Key? key, required Spi spi, String? initPath, bool isSelect = false}) {
return AppRoutes(
SftpPage(
key: key,
spi: spi,
initPath: initPath,
isSelect: isSelect,
),
'sftp');
}
static AppRoutes docker({Key? key, required Spi spi}) {
return AppRoutes(ContainerPage(key: key, spi: spi), 'docker');
}
// static AppRoutes fullscreen({Key? key}) {
// return AppRoutes(FullScreenPage(key: key), 'fullscreen');
// }
static AppRoutes home({Key? key}) {
return AppRoutes(HomePage(key: key), 'home');
}
static AppRoutes ping({Key? key}) {
return AppRoutes(PingPage(key: key), 'ping');
}
static AppRoutes process({Key? key, required Spi spi}) {
return AppRoutes(ProcessPage(key: key, spi: spi), 'process');
}
static AppRoutes serverOrder({Key? key}) {
return AppRoutes(ServerOrderPage(key: key), 'server_order');
}
static AppRoutes serverDetailOrder({Key? key}) {
return AppRoutes(ServerDetailOrderPage(key: key), 'server_detail_order');
}
static AppRoutes iosSettings({Key? key}) {
return AppRoutes(IOSSettingsPage(key: key), 'ios_setting');
}
static AppRoutes androidSettings({Key? key}) {
return AppRoutes(AndroidSettingsPage(key: key), 'android_setting');
}
static AppRoutes snippetResult(
{Key? key, required List<SnippetResult?> results}) {
return AppRoutes(
SnippetResultPage(
key: key,
results: results,
),
'snippet_result');
}
static AppRoutes iperf({Key? key, required Spi spi}) {
return AppRoutes(IPerfPage(key: key, spi: spi), 'iperf');
}
static AppRoutes serverFuncBtnsOrder({Key? key}) {
return AppRoutes(ServerFuncBtnsOrderPage(key: key), 'server_func_btns_seq');
}
static AppRoutes pve({Key? key, required Spi spi}) {
return AppRoutes(PvePage(key: key, spi: spi), 'pve');
}
} }

View File

@@ -1,39 +1,32 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/app/backup.dart'; import 'package:server_box/data/model/app/bak/backup2.dart';
import 'package:server_box/data/store/no_backup.dart'; import 'package:server_box/data/model/app/bak/utils.dart';
const bakSync = BakSyncer._(); const bakSync = BakSyncer._();
final class BakSyncer extends SyncIface<Backup> { final icloud = ICloud(containerId: 'iCloud.tech.lolli.serverbox');
final class BakSyncer extends SyncIface {
const BakSyncer._() : super(); const BakSyncer._() : super();
@override @override
Future<void> saveToFile() => Backup.backup(); Future<void> saveToFile() => BackupV2.backup();
@override @override
Future<Backup> fromFile(String path) async { Future<Mergeable> fromFile(String path) async {
final content = await File(path).readAsString(); final content = await File(path).readAsString();
return Backup.fromJsonString(content); return MergeableUtils.fromJsonString(content).$1;
} }
@override @override
Future<RemoteStorage?> get remoteStorage async { RemoteStorage? get remoteStorage {
if (isMacOS || isIOS) await icloud.init('iCloud.tech.lolli.serverbox'); final icloudEnabled = PrefProps.icloudSync.get();
final settings = NoBackupStore.instance;
await webdav.init(WebdavInitArgs(
url: settings.webdavUrl.fetch(),
user: settings.webdavUser.fetch(),
pwd: settings.webdavPwd.fetch(),
prefix: 'serverbox/',
));
final icloudEnabled = settings.icloudSync.fetch();
if (icloudEnabled) return icloud; if (icloudEnabled) return icloud;
final webdavEnabled = settings.webdavSync.fetch(); final webdavEnabled = PrefProps.webdavSync.get();
if (webdavEnabled) return webdav; if (webdavEnabled) return Webdav.shared;
return null; return null;
} }

View File

@@ -4,9 +4,8 @@ import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:server_box/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/res/store.dart';
/// Must put this func out of any Class. /// Must put this func out of any Class.
/// ///
@@ -32,7 +31,7 @@ enum GenSSHClientStatus {
} }
String getPrivateKey(String id) { String getPrivateKey(String id) {
final pki = Stores.key.get(id); final pki = Stores.key.fetchOne(id);
if (pki == null) { if (pki == null) {
throw SSHErr( throw SSHErr(
type: SSHErrType.noPrivateKey, type: SSHErrType.noPrivateKey,

View File

@@ -1,37 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Backup _$BackupFromJson(Map<String, dynamic> json) => Backup(
version: (json['version'] as num).toInt(),
date: json['date'] as String,
spis: (json['spis'] as List<dynamic>)
.map((e) => Spi.fromJson(e as Map<String, dynamic>))
.toList(),
snippets: (json['snippets'] as List<dynamic>)
.map((e) => Snippet.fromJson(e as Map<String, dynamic>))
.toList(),
keys: (json['keys'] as List<dynamic>)
.map((e) => PrivateKeyInfo.fromJson(e as Map<String, dynamic>))
.toList(),
container: json['container'] as Map<String, dynamic>,
history: json['history'] as Map<String, dynamic>,
settings: json['settings'] as Map<String, dynamic>?,
lastModTime: (json['lastModTime'] as num?)?.toInt(),
);
Map<String, dynamic> _$BackupToJson(Backup instance) => <String, dynamic>{
'version': instance.version,
'date': instance.date,
'spis': instance.spis,
'snippets': instance.snippets,
'keys': instance.keys,
'container': instance.container,
'history': instance.history,
'lastModTime': instance.lastModTime,
'settings': instance.settings,
};

View File

@@ -8,7 +8,6 @@ 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/server_private_info.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/rebuild.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
part 'backup.g.dart'; part 'backup.g.dart';
@@ -46,19 +45,24 @@ class Backup implements Mergeable {
Map<String, dynamic> toJson() => _$BackupToJson(this); Map<String, dynamic> toJson() => _$BackupToJson(this);
Backup.loadFromStore() static Future<Backup> loadFromStore() async {
: version = backupFormatVersion, final lastModTime = Stores.lastModTime;
date = DateTime.now().toString().split('.').firstOrNull ?? '', return Backup(
spis = Stores.server.fetch(), version: backupFormatVersion,
snippets = Stores.snippet.fetch(), date: DateTime.now().toString().split('.').firstOrNull ?? '',
keys = Stores.key.fetch(), spis: Stores.server.fetch(),
container = Stores.container.box.toJson(), snippets: Stores.snippet.fetch(),
lastModTime = Stores.lastModTime, keys: Stores.key.fetch(),
history = Stores.history.box.toJson(), container: Stores.container.getAllMap(),
settings = Stores.setting.box.toJson(); lastModTime: lastModTime,
history: Stores.history.getAllMap(),
settings: Stores.setting.getAllMap(),
);
}
static Future<String> backup([String? name]) async { static Future<String> backup([String? name]) async {
final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson())); final bak = await Backup.loadFromStore();
final result = _diyEncrypt(json.encode(bak.toJson()));
final path = Paths.doc.joinPath(name ?? Miscs.bakFileName); final path = Paths.doc.joinPath(name ?? Miscs.bakFileName);
await File(path).writeAsString(result); await File(path).writeAsString(result);
return path; return path;
@@ -66,7 +70,7 @@ class Backup implements Mergeable {
@override @override
Future<void> merge({bool force = false}) async { Future<void> merge({bool force = false}) async {
final curTime = Stores.lastModTime ?? 0; final curTime = Stores.lastModTime;
final bakTime = lastModTime ?? 0; final bakTime = lastModTime ?? 0;
final shouldRestore = force || curTime < bakTime; final shouldRestore = force || curTime < bakTime;
if (!shouldRestore) { if (!shouldRestore) {
@@ -230,3 +234,4 @@ String _diyDecrypt(String raw) {
rethrow; rethrow;
} }
} }

View File

@@ -0,0 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Backup _$BackupFromJson(Map<String, dynamic> json) => Backup(
version: (json['version'] as num).toInt(),
date: json['date'] as String,
spis: (json['spis'] as List<dynamic>)
.map((e) => Spi.fromJson(e as Map<String, dynamic>))
.toList(),
snippets: (json['snippets'] as List<dynamic>)
.map((e) => Snippet.fromJson(e as Map<String, dynamic>))
.toList(),
keys: (json['keys'] as List<dynamic>)
.map((e) => PrivateKeyInfo.fromJson(e as Map<String, dynamic>))
.toList(),
container: json['container'] as Map<String, dynamic>,
history: json['history'] as Map<String, dynamic>,
settings: json['settings'] as Map<String, dynamic>?,
lastModTime: (json['lastModTime'] as num?)?.toInt(),
);
Map<String, dynamic> _$BackupToJson(Backup instance) => <String, dynamic>{
'version': instance.version,
'date': instance.date,
'spis': instance.spis,
'snippets': instance.snippets,
'keys': instance.keys,
'container': instance.container,
'history': instance.history,
'lastModTime': instance.lastModTime,
'settings': instance.settings,
};

View File

@@ -0,0 +1,89 @@
import 'dart:convert';
import 'dart:io';
import 'package:fl_lib/fl_lib.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:logging/logging.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart';
part 'backup2.freezed.dart';
part 'backup2.g.dart';
final _loggerV2 = Logger('BackupV2');
@freezed
abstract class BackupV2 with _$BackupV2 implements Mergeable {
const BackupV2._();
/// Construct a backup with the latest format (v2).
///
/// All `Map<String, dynamic>` are:
/// ```json
/// {
/// "key1": Model{},
/// "_lastModTime": {
/// "key1": 1234567890,
/// },
/// }
/// ```
const factory BackupV2({
required int version,
required int date,
required Map<String, Object?> spis,
required Map<String, Object?> snippets,
required Map<String, Object?> keys,
required Map<String, Object?> container,
required Map<String, Object?> history,
required Map<String, Object?> settings,
}) = _BackupV2;
factory BackupV2.fromJson(Map<String, dynamic> json) => _$BackupV2FromJson(json);
@override
Future<void> merge({bool force = false}) async {
_loggerV2.info('Merging...');
// Merge each store
await Mergeable.mergeStore(backupData: spis, store: Stores.server, force: force);
await Mergeable.mergeStore(backupData: snippets, store: Stores.snippet, force: force);
await Mergeable.mergeStore(backupData: keys, store: Stores.key, force: force);
await Mergeable.mergeStore(backupData: container, store: Stores.container, force: force);
await Mergeable.mergeStore(backupData: history, store: Stores.history, force: force);
await Mergeable.mergeStore(backupData: settings, store: Stores.setting, force: force);
// Reload providers and notify listeners
Provider.reload();
RNodes.app.notify();
_loggerV2.info('Merge completed');
}
static const formatVer = 2;
static Future<BackupV2> loadFromStore() async {
return BackupV2(
version: formatVer,
date: DateTimeX.timestamp,
spis: Stores.server.getAllMap(includeInternalKeys: true),
snippets: Stores.snippet.getAllMap(includeInternalKeys: true),
keys: Stores.key.getAllMap(includeInternalKeys: true),
container: Stores.container.getAllMap(includeInternalKeys: true),
history: Stores.history.getAllMap(includeInternalKeys: true),
settings: Stores.setting.getAllMap(includeInternalKeys: true),
);
}
static Future<String> backup([String? name]) async {
final bak = await BackupV2.loadFromStore();
final result = json.encode(bak.toJson());
final path = Paths.doc.joinPath(name ?? Miscs.bakFileName);
await File(path).writeAsString(result);
return path;
}
factory BackupV2.fromJsonString(String jsonString) {
final map = json.decode(jsonString) as Map<String, dynamic>;
return BackupV2.fromJson(map);
}
}

View File

@@ -0,0 +1,372 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'backup2.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
BackupV2 _$BackupV2FromJson(Map<String, dynamic> json) {
return _BackupV2.fromJson(json);
}
/// @nodoc
mixin _$BackupV2 {
int get version => throw _privateConstructorUsedError;
int get date => throw _privateConstructorUsedError;
Map<String, Object?> get spis => throw _privateConstructorUsedError;
Map<String, Object?> get snippets => throw _privateConstructorUsedError;
Map<String, Object?> get keys => throw _privateConstructorUsedError;
Map<String, Object?> get container => throw _privateConstructorUsedError;
Map<String, Object?> get history => throw _privateConstructorUsedError;
Map<String, Object?> get settings => throw _privateConstructorUsedError;
/// Serializes this BackupV2 to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of BackupV2
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$BackupV2CopyWith<BackupV2> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $BackupV2CopyWith<$Res> {
factory $BackupV2CopyWith(BackupV2 value, $Res Function(BackupV2) then) =
_$BackupV2CopyWithImpl<$Res, BackupV2>;
@useResult
$Res call({
int version,
int date,
Map<String, Object?> spis,
Map<String, Object?> snippets,
Map<String, Object?> keys,
Map<String, Object?> container,
Map<String, Object?> history,
Map<String, Object?> settings,
});
}
/// @nodoc
class _$BackupV2CopyWithImpl<$Res, $Val extends BackupV2>
implements $BackupV2CopyWith<$Res> {
_$BackupV2CopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of BackupV2
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? version = null,
Object? date = null,
Object? spis = null,
Object? snippets = null,
Object? keys = null,
Object? container = null,
Object? history = null,
Object? settings = null,
}) {
return _then(
_value.copyWith(
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as int,
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as int,
spis: null == spis
? _value.spis
: spis // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
snippets: null == snippets
? _value.snippets
: snippets // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
keys: null == keys
? _value.keys
: keys // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
container: null == container
? _value.container
: container // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
history: null == history
? _value.history
: history // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
settings: null == settings
? _value.settings
: settings // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$BackupV2ImplCopyWith<$Res>
implements $BackupV2CopyWith<$Res> {
factory _$$BackupV2ImplCopyWith(
_$BackupV2Impl value,
$Res Function(_$BackupV2Impl) then,
) = __$$BackupV2ImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
int version,
int date,
Map<String, Object?> spis,
Map<String, Object?> snippets,
Map<String, Object?> keys,
Map<String, Object?> container,
Map<String, Object?> history,
Map<String, Object?> settings,
});
}
/// @nodoc
class __$$BackupV2ImplCopyWithImpl<$Res>
extends _$BackupV2CopyWithImpl<$Res, _$BackupV2Impl>
implements _$$BackupV2ImplCopyWith<$Res> {
__$$BackupV2ImplCopyWithImpl(
_$BackupV2Impl _value,
$Res Function(_$BackupV2Impl) _then,
) : super(_value, _then);
/// Create a copy of BackupV2
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? version = null,
Object? date = null,
Object? spis = null,
Object? snippets = null,
Object? keys = null,
Object? container = null,
Object? history = null,
Object? settings = null,
}) {
return _then(
_$BackupV2Impl(
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as int,
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as int,
spis: null == spis
? _value._spis
: spis // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
snippets: null == snippets
? _value._snippets
: snippets // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
keys: null == keys
? _value._keys
: keys // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
container: null == container
? _value._container
: container // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
history: null == history
? _value._history
: history // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
settings: null == settings
? _value._settings
: settings // ignore: cast_nullable_to_non_nullable
as Map<String, Object?>,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$BackupV2Impl extends _BackupV2 {
const _$BackupV2Impl({
required this.version,
required this.date,
required final Map<String, Object?> spis,
required final Map<String, Object?> snippets,
required final Map<String, Object?> keys,
required final Map<String, Object?> container,
required final Map<String, Object?> history,
required final Map<String, Object?> settings,
}) : _spis = spis,
_snippets = snippets,
_keys = keys,
_container = container,
_history = history,
_settings = settings,
super._();
factory _$BackupV2Impl.fromJson(Map<String, dynamic> json) =>
_$$BackupV2ImplFromJson(json);
@override
final int version;
@override
final int date;
final Map<String, Object?> _spis;
@override
Map<String, Object?> get spis {
if (_spis is EqualUnmodifiableMapView) return _spis;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_spis);
}
final Map<String, Object?> _snippets;
@override
Map<String, Object?> get snippets {
if (_snippets is EqualUnmodifiableMapView) return _snippets;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_snippets);
}
final Map<String, Object?> _keys;
@override
Map<String, Object?> get keys {
if (_keys is EqualUnmodifiableMapView) return _keys;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_keys);
}
final Map<String, Object?> _container;
@override
Map<String, Object?> get container {
if (_container is EqualUnmodifiableMapView) return _container;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_container);
}
final Map<String, Object?> _history;
@override
Map<String, Object?> get history {
if (_history is EqualUnmodifiableMapView) return _history;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_history);
}
final Map<String, Object?> _settings;
@override
Map<String, Object?> get settings {
if (_settings is EqualUnmodifiableMapView) return _settings;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_settings);
}
@override
String toString() {
return 'BackupV2(version: $version, date: $date, spis: $spis, snippets: $snippets, keys: $keys, container: $container, history: $history, settings: $settings)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$BackupV2Impl &&
(identical(other.version, version) || other.version == version) &&
(identical(other.date, date) || other.date == date) &&
const DeepCollectionEquality().equals(other._spis, _spis) &&
const DeepCollectionEquality().equals(other._snippets, _snippets) &&
const DeepCollectionEquality().equals(other._keys, _keys) &&
const DeepCollectionEquality().equals(
other._container,
_container,
) &&
const DeepCollectionEquality().equals(other._history, _history) &&
const DeepCollectionEquality().equals(other._settings, _settings));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
version,
date,
const DeepCollectionEquality().hash(_spis),
const DeepCollectionEquality().hash(_snippets),
const DeepCollectionEquality().hash(_keys),
const DeepCollectionEquality().hash(_container),
const DeepCollectionEquality().hash(_history),
const DeepCollectionEquality().hash(_settings),
);
/// Create a copy of BackupV2
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$BackupV2ImplCopyWith<_$BackupV2Impl> get copyWith =>
__$$BackupV2ImplCopyWithImpl<_$BackupV2Impl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$BackupV2ImplToJson(this);
}
}
abstract class _BackupV2 extends BackupV2 {
const factory _BackupV2({
required final int version,
required final int date,
required final Map<String, Object?> spis,
required final Map<String, Object?> snippets,
required final Map<String, Object?> keys,
required final Map<String, Object?> container,
required final Map<String, Object?> history,
required final Map<String, Object?> settings,
}) = _$BackupV2Impl;
const _BackupV2._() : super._();
factory _BackupV2.fromJson(Map<String, dynamic> json) =
_$BackupV2Impl.fromJson;
@override
int get version;
@override
int get date;
@override
Map<String, Object?> get spis;
@override
Map<String, Object?> get snippets;
@override
Map<String, Object?> get keys;
@override
Map<String, Object?> get container;
@override
Map<String, Object?> get history;
@override
Map<String, Object?> get settings;
/// Create a copy of BackupV2
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$BackupV2ImplCopyWith<_$BackupV2Impl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'backup2.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$BackupV2Impl _$$BackupV2ImplFromJson(Map<String, dynamic> json) =>
_$BackupV2Impl(
version: (json['version'] as num).toInt(),
date: (json['date'] as num).toInt(),
spis: json['spis'] as Map<String, dynamic>,
snippets: json['snippets'] as Map<String, dynamic>,
keys: json['keys'] as Map<String, dynamic>,
container: json['container'] as Map<String, dynamic>,
history: json['history'] as Map<String, dynamic>,
settings: json['settings'] as Map<String, dynamic>,
);
Map<String, dynamic> _$$BackupV2ImplToJson(_$BackupV2Impl instance) =>
<String, dynamic>{
'version': instance.version,
'date': instance.date,
'spis': instance.spis,
'snippets': instance.snippets,
'keys': instance.keys,
'container': instance.container,
'history': instance.history,
'settings': instance.settings,
};

View File

@@ -0,0 +1,15 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/app/bak/backup.dart';
import 'package:server_box/data/model/app/bak/backup2.dart';
abstract final class MergeableUtils {
static (Mergeable, String) fromJsonString(String json) {
try {
final bak = BackupV2.fromJsonString(json);
return (bak, DateTime.fromMillisecondsSinceEpoch(bak.date).hms());
} catch (e) {
final bak = Backup.fromJsonString(json);
return (bak, bak.date);
}
}
}

View File

@@ -1,27 +1,6 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
enum ErrFrom {
unknown,
apt,
docker,
sftp,
ssh,
status,
icloud,
webdav,
;
}
abstract class Err<T> {
final ErrFrom from;
final T type;
final String? message;
String? get solution;
Err({required this.from, required this.type, this.message});
}
enum SSHErrType { enum SSHErrType {
unknown, unknown,
connect, connect,
@@ -35,7 +14,7 @@ enum SSHErrType {
} }
class SSHErr extends Err<SSHErrType> { class SSHErr extends Err<SSHErrType> {
SSHErr({required super.type, super.message}) : super(from: ErrFrom.ssh); SSHErr({required super.type, super.message});
@override @override
String? get solution => switch (type) { String? get solution => switch (type) {
@@ -45,11 +24,6 @@ class SSHErr extends Err<SSHErrType> {
SSHErrType.noPrivateKey => l10n.noPrivateKeyTip, SSHErrType.noPrivateKey => l10n.noPrivateKeyTip,
_ => null, _ => null,
}; };
@override
String toString() {
return 'SSHErr<$type>: $message';
}
} }
enum ContainerErrType { enum ContainerErrType {
@@ -65,16 +39,10 @@ enum ContainerErrType {
} }
class ContainerErr extends Err<ContainerErrType> { class ContainerErr extends Err<ContainerErrType> {
ContainerErr({required super.type, super.message}) ContainerErr({required super.type, super.message});
: super(from: ErrFrom.docker);
@override @override
String? get solution => null; String? get solution => null;
@override
String toString() {
return 'ContainerErr<$type>: $message';
}
} }
enum ICloudErrType { enum ICloudErrType {
@@ -84,15 +52,10 @@ enum ICloudErrType {
} }
class ICloudErr extends Err<ICloudErrType> { class ICloudErr extends Err<ICloudErrType> {
ICloudErr({required super.type, super.message}) : super(from: ErrFrom.icloud); ICloudErr({required super.type, super.message});
@override @override
String? get solution => null; String? get solution => null;
@override
String toString() {
return 'ICloudErr<$type>: $message';
}
} }
enum WebdavErrType { enum WebdavErrType {
@@ -102,15 +65,10 @@ enum WebdavErrType {
} }
class WebdavErr extends Err<WebdavErrType> { class WebdavErr extends Err<WebdavErrType> {
WebdavErr({required super.type, super.message}) : super(from: ErrFrom.webdav); WebdavErr({required super.type, super.message});
@override @override
String? get solution => null; String? get solution => null;
@override
String toString() {
return 'WebdavErr<$type>: $message';
}
} }
enum PveErrType { enum PveErrType {
@@ -121,13 +79,8 @@ enum PveErrType {
} }
class PveErr extends Err<PveErrType> { class PveErr extends Err<PveErrType> {
PveErr({required super.type, super.message}) : super(from: ErrFrom.status); PveErr({required super.type, super.message});
@override @override
String? get solution => null; String? get solution => null;
@override
String toString() {
return 'PveErr<$type>: $message';
}
} }

View File

@@ -1,36 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
part 'server_func.g.dart';
@HiveType(typeId: 6)
enum ServerFuncBtn { enum ServerFuncBtn {
@HiveField(0) terminal(),
terminal._(), sftp(),
@HiveField(1) container(),
sftp._(), process(),
@HiveField(2) //pkg(),
container._(), snippet(),
@HiveField(3) iperf(),
process._(), // pve(),
//@HiveField(4) systemd(1058),
//pkg,
@HiveField(5)
snippet._(),
@HiveField(6)
iperf._(),
// @HiveField(7)
// pve,
@HiveField(8)
systemd._(1058),
; ;
final int? addedVersion; final int? addedVersion;
const ServerFuncBtn._([this.addedVersion]); const ServerFuncBtn([this.addedVersion]);
static void autoAddNewFuncs(int cur) { static void autoAddNewFuncs(int cur) {
if (cur >= systemd.addedVersion!) { if (cur >= systemd.addedVersion!) {

View File

@@ -1,71 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_func.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
@override
final int typeId = 6;
@override
ServerFuncBtn read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return ServerFuncBtn.terminal;
case 1:
return ServerFuncBtn.sftp;
case 2:
return ServerFuncBtn.container;
case 3:
return ServerFuncBtn.process;
case 5:
return ServerFuncBtn.snippet;
case 6:
return ServerFuncBtn.iperf;
case 8:
return ServerFuncBtn.systemd;
default:
return ServerFuncBtn.terminal;
}
}
@override
void write(BinaryWriter writer, ServerFuncBtn obj) {
switch (obj) {
case ServerFuncBtn.terminal:
writer.writeByte(0);
break;
case ServerFuncBtn.sftp:
writer.writeByte(1);
break;
case ServerFuncBtn.container:
writer.writeByte(2);
break;
case ServerFuncBtn.process:
writer.writeByte(3);
break;
case ServerFuncBtn.snippet:
writer.writeByte(5);
break;
case ServerFuncBtn.iperf:
writer.writeByte(6);
break;
case ServerFuncBtn.systemd:
writer.writeByte(8);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerFuncBtnAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -1,17 +1,10 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
part 'net_view.g.dart';
@HiveType(typeId: 5)
enum NetViewType { enum NetViewType {
@HiveField(0)
conn, conn,
@HiveField(1)
speed, speed,
@HiveField(2)
traffic; traffic;
NetViewType get next => switch (this) { NetViewType get next => switch (this) {

View File

@@ -1,51 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'net_view.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class NetViewTypeAdapter extends TypeAdapter<NetViewType> {
@override
final int typeId = 5;
@override
NetViewType read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return NetViewType.conn;
case 1:
return NetViewType.speed;
case 2:
return NetViewType.traffic;
default:
return NetViewType.conn;
}
}
@override
void write(BinaryWriter writer, NetViewType obj) {
switch (obj) {
case NetViewType.conn:
writer.writeByte(0);
break;
case NetViewType.speed:
writer.writeByte(1);
break;
case NetViewType.traffic:
writer.writeByte(2);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NetViewTypeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -11,13 +11,13 @@ enum ServerDetailCards {
swap(Icons.swap_horiz), swap(Icons.swap_horiz),
gpu(Bootstrap.gpu_card), gpu(Bootstrap.gpu_card),
disk(Bootstrap.device_hdd_fill), disk(Bootstrap.device_hdd_fill),
smart(Icons.health_and_safety, sinceBuild: 1174),
net(ZondIcons.network), net(ZondIcons.network),
sensor(MingCute.dashboard_4_line), sensor(MingCute.dashboard_4_line),
temp(FontAwesome.temperature_empty_solid), temp(FontAwesome.temperature_empty_solid),
battery(Icons.battery_full), battery(Icons.battery_full),
pve(BoxIcons.bxs_dashboard, sinceBuild: 818), pve(BoxIcons.bxs_dashboard, sinceBuild: 818),
custom(Icons.code, sinceBuild: 825), custom(Icons.code, sinceBuild: 825);
;
final int? sinceBuild; final int? sinceBuild;
@@ -31,19 +31,20 @@ enum ServerDetailCards {
static final names = values.map((e) => e.name).toList(); static final names = values.map((e) => e.name).toList();
String get toStr => switch (this) { String get toStr => switch (this) {
about => libL10n.about, about => libL10n.about,
cpu => 'CPU', cpu => 'CPU',
mem => 'RAM', mem => 'RAM',
swap => 'Swap', swap => 'Swap',
gpu => 'GPU', gpu => 'GPU',
disk => l10n.disk, disk => l10n.disk,
net => l10n.net, smart => l10n.diskHealth,
sensor => l10n.sensors, net => l10n.net,
temp => l10n.temperature, sensor => l10n.sensors,
battery => l10n.battery, temp => l10n.temperature,
pve => 'PVE', battery => l10n.battery,
custom => l10n.cmd, pve => 'PVE',
}; custom => l10n.cmd,
};
/// If: /// If:
/// Version 1 => user set [about], default is [about, cpu] /// Version 1 => user set [about], default is [about, cpu]

View File

@@ -1,8 +1,7 @@
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/res/build_data.dart';
enum ShellFunc { enum ShellFunc {
status, status,
@@ -10,8 +9,7 @@ enum ShellFunc {
process, process,
shutdown, shutdown,
reboot, reboot,
suspend, suspend;
;
static const seperator = 'SrvBoxSep'; static const seperator = 'SrvBoxSep';
@@ -30,8 +28,9 @@ enum ShellFunc {
/// Default is [scriptDirTmp]/[scriptFile], if this path is not accessible, /// Default is [scriptDirTmp]/[scriptFile], if this path is not accessible,
/// it will be changed to [scriptDirHome]/[scriptFile]. /// it will be changed to [scriptDirHome]/[scriptFile].
static String getScriptDir(String id) { static String getScriptDir(String id) {
final customScriptDir = final customScriptDir = ServerProvider.pick(
ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir; id: id,
)?.value.spi.custom?.scriptDir;
if (customScriptDir != null) return customScriptDir; if (customScriptDir != null) return customScriptDir;
return _scriptDirMap.putIfAbsent(id, () { return _scriptDirMap.putIfAbsent(id, () {
return scriptDirTmp; return scriptDirTmp;
@@ -39,10 +38,10 @@ enum ShellFunc {
} }
static void switchScriptDir(String id) => switch (_scriptDirMap[id]) { static void switchScriptDir(String id) => switch (_scriptDirMap[id]) {
scriptDirTmp => _scriptDirMap[id] = scriptDirHome, scriptDirTmp => _scriptDirMap[id] = scriptDirHome,
scriptDirHome => _scriptDirMap[id] = scriptDirTmp, scriptDirHome => _scriptDirMap[id] = scriptDirTmp,
_ => _scriptDirMap[id] = scriptDirHome, _ => _scriptDirMap[id] = scriptDirHome,
}; };
static String getScriptPath(String id) { static String getScriptPath(String id) {
return '${getScriptDir(id)}/$scriptFile'; return '${getScriptDir(id)}/$scriptFile';
@@ -59,13 +58,13 @@ chmod 755 $scriptPath
} }
String get flag => switch (this) { String get flag => switch (this) {
ShellFunc.process => 'p', ShellFunc.process => 'p',
ShellFunc.shutdown => 'sd', ShellFunc.shutdown => 'sd',
ShellFunc.reboot => 'r', ShellFunc.reboot => 'r',
ShellFunc.suspend => 'sp', ShellFunc.suspend => 'sp',
ShellFunc.status => 's', ShellFunc.status => 's',
// ShellFunc.docker=> 'd', // ShellFunc.docker=> 'd',
}; };
String exec(String id) => 'sh ${getScriptPath(id)} -$flag'; String exec(String id) => 'sh ${getScriptPath(id)} -$flag';
@@ -96,14 +95,14 @@ if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
else else
\t${BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider)} \t${BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider)}
fi'''; fi''';
// case ShellFunc.docker: // case ShellFunc.docker:
// return ''' // return '''
// result=\$(docker version 2>&1 | grep "permission denied") // result=\$(docker version 2>&1 | grep "permission denied")
// if [ "\$result" != "" ]; then // if [ "\$result" != "" ]; then
// \t${_dockerCmds.join(_cmdDivider)} // \t${_dockerCmds.join(_cmdDivider)}
// else // else
// \t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)} // \t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)}
// fi'''; // fi''';
case ShellFunc.process: case ShellFunc.process:
return ''' return '''
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
@@ -209,22 +208,25 @@ enum StatusCmdType {
echo._('echo ${SystemType.linuxSign}'), echo._('echo ${SystemType.linuxSign}'),
time._('date +%s'), time._('date +%s'),
net._('cat /proc/net/dev'), net._('cat /proc/net/dev'),
sys._('cat /etc/*-release | grep PRETTY_NAME'), sys._('cat /etc/*-release | grep ^PRETTY_NAME'),
cpu._('cat /proc/stat | grep cpu'), cpu._('cat /proc/stat | grep cpu'),
uptime._('uptime'), uptime._('uptime'),
conn._('cat /proc/net/snmp'), conn._('cat /proc/net/snmp'),
disk._('df'), disk._(
'lsblk --bytes --json --output FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID',
),
mem._("cat /proc/meminfo | grep -E 'Mem|Swap'"), mem._("cat /proc/meminfo | grep -E 'Mem|Swap'"),
tempType._('cat /sys/class/thermal/thermal_zone*/type'), tempType._('cat /sys/class/thermal/thermal_zone*/type'),
tempVal._('cat /sys/class/thermal/thermal_zone*/temp'), tempVal._('cat /sys/class/thermal/thermal_zone*/temp'),
host._('cat /etc/hostname'), host._('cat /etc/hostname'),
diskio._('cat /proc/diskstats'), diskio._('cat /proc/diskstats'),
battery._( battery._(
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'), 'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done',
),
nvidia._('nvidia-smi -q -x'), nvidia._('nvidia-smi -q -x'),
sensors._('sensors'), sensors._('sensors'),
cpuBrand._('cat /proc/cpuinfo | grep "model name"'), diskSmart._('for d in \$(lsblk -dn -o KNAME); do smartctl -j /dev/\$d; echo; done'),
; cpuBrand._('cat /proc/cpuinfo | grep "model name"');
final String cmd; final String cmd;
@@ -238,12 +240,12 @@ enum BSDStatusCmdType {
sys._('uname -or'), sys._('uname -or'),
cpu._('top -l 1 | grep "CPU usage"'), cpu._('top -l 1 | grep "CPU usage"'),
uptime._('uptime'), uptime._('uptime'),
// Keep df -k for BSD systems as lsblk is not available on macOS/BSD
disk._('df -k'), disk._('df -k'),
mem._('top -l 1 | grep PhysMem'), mem._('top -l 1 | grep PhysMem'),
//temp, //temp,
host._('hostname'), host._('hostname'),
cpuBrand._('sysctl -n machdep.cpu.brand_string'), cpuBrand._('sysctl -n machdep.cpu.brand_string');
;
final String cmd; final String cmd;
@@ -252,10 +254,10 @@ enum BSDStatusCmdType {
extension StatusCmdTypeX on StatusCmdType { extension StatusCmdTypeX on StatusCmdType {
String get i18n => switch (this) { String get i18n => switch (this) {
StatusCmdType.sys => l10n.system, StatusCmdType.sys => l10n.system,
StatusCmdType.host => l10n.host, StatusCmdType.host => l10n.host,
StatusCmdType.uptime => l10n.uptime, StatusCmdType.uptime => l10n.uptime,
StatusCmdType.battery => l10n.battery, StatusCmdType.battery => l10n.battery,
final val => val.name, final val => val.name,
}; };
} }

View File

@@ -1,24 +0,0 @@
import 'dart:async';
class SyncResult<T, E> {
final List<T> up;
final List<T> down;
final Map<T, E> err;
const SyncResult({
required this.up,
required this.down,
required this.err,
});
@override
String toString() {
return 'SyncResult{up: $up, down: $down, err: $err}';
}
}
abstract class SyncIface<T> {
/// Merge [other] into [this], return [this] after merge.
/// Data in [other] has higher priority than [this].
FutureOr<void> sync(T other);
}

View File

@@ -1,11 +1,11 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/view/page/server/tab.dart'; import 'package:server_box/view/page/server/tab/tab.dart';
import 'package:server_box/view/page/setting/entry.dart'; // import 'package:server_box/view/page/setting/entry.dart';
import 'package:server_box/view/page/snippet/list.dart'; import 'package:server_box/view/page/snippet/list.dart';
import 'package:server_box/view/page/ssh/tab.dart'; import 'package:server_box/view/page/ssh/tab.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/view/page/storage/local.dart'; import 'package:server_box/view/page/storage/local.dart';
enum AppTab { enum AppTab {
@@ -13,13 +13,13 @@ enum AppTab {
ssh, ssh,
file, file,
snippet, snippet,
settings, //settings,
; ;
Widget get page { Widget get page {
return switch (this) { return switch (this) {
server => const ServerPage(), server => const ServerPage(),
settings => const SettingsPage(), //settings => const SettingsPage(),
ssh => const SSHTabPage(), ssh => const SSHTabPage(),
file => const LocalFilePage(), file => const LocalFilePage(),
snippet => const SnippetListPage(), snippet => const SnippetListPage(),
@@ -33,11 +33,11 @@ enum AppTab {
label: l10n.server, label: l10n.server,
selectedIcon: const Icon(BoxIcons.bxs_server), selectedIcon: const Icon(BoxIcons.bxs_server),
), ),
settings => NavigationDestination( // settings => NavigationDestination(
icon: const Icon(Icons.settings), // icon: const Icon(Icons.settings),
label: libL10n.setting, // label: libL10n.setting,
selectedIcon: const Icon(Icons.settings), // selectedIcon: const Icon(Icons.settings),
), // ),
ssh => const NavigationDestination( ssh => const NavigationDestination(
icon: Icon(Icons.terminal_outlined), icon: Icon(Icons.terminal_outlined),
label: 'SSH', label: 'SSH',
@@ -56,7 +56,41 @@ enum AppTab {
}; };
} }
NavigationRailDestination get navRailDestination {
return switch (this) {
server => NavigationRailDestination(
icon: const Icon(BoxIcons.bx_server),
label: Text(l10n.server),
selectedIcon: const Icon(BoxIcons.bxs_server),
),
// settings => NavigationRailDestination(
// icon: const Icon(Icons.settings),
// label: libL10n.setting,
// selectedIcon: const Icon(Icons.settings),
// ),
ssh => const NavigationRailDestination(
icon: Icon(Icons.terminal_outlined),
label: Text('SSH'),
selectedIcon: Icon(Icons.terminal),
),
snippet => NavigationRailDestination(
icon: const Icon(Icons.code),
label: Text(l10n.snippet),
selectedIcon: const Icon(Icons.code),
),
file => NavigationRailDestination(
icon: const Icon(Icons.folder_open),
label: Text(libL10n.file),
selectedIcon: const Icon(Icons.folder),
),
};
}
static List<NavigationDestination> get navDestinations { static List<NavigationDestination> get navDestinations {
return AppTab.values.map((e) => e.navDestination).toList(); return AppTab.values.map((e) => e.navDestination).toList();
} }
static List<NavigationRailDestination> get navRailDestinations {
return AppTab.values.map((e) => e.navRailDestination).toList();
}
} }

View File

@@ -22,8 +22,6 @@ enum PkgManager {
return 'opkg list-upgradable'; return 'opkg list-upgradable';
case PkgManager.apk: case PkgManager.apk:
return 'apk list --upgradable'; return 'apk list --upgradable';
default:
return null;
} }
} }
@@ -56,8 +54,6 @@ enum PkgManager {
return 'opkg upgrade $args'; return 'opkg upgrade $args';
case PkgManager.apk: case PkgManager.apk:
return 'apk upgrade'; return 'apk upgrade';
default:
return null;
} }
} }
@@ -109,6 +105,7 @@ enum PkgManager {
return PkgManager.apt; return PkgManager.apt;
case Dist.opensuse: case Dist.opensuse:
return PkgManager.zypper; return PkgManager.zypper;
case Dist.coreelec:
case Dist.wrt: case Dist.wrt:
return PkgManager.opkg; return PkgManager.opkg;
case Dist.arch: case Dist.arch:

View File

@@ -1,32 +1,27 @@
import 'package:hive_flutter/adapters.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'custom.g.dart'; part 'custom.g.dart';
@JsonSerializable() @JsonSerializable(includeIfNull: false)
@HiveType(typeId: 7)
final class ServerCustom { final class ServerCustom {
// @HiveField(0) // @HiveField(0)
// final String? temperature; // final String? temperature;
@HiveField(1)
final String? pveAddr; final String? pveAddr;
@HiveField(2, defaultValue: false)
final bool pveIgnoreCert; final bool pveIgnoreCert;
/// {"title": "cmd"} /// {"title": "cmd"}
@HiveField(3)
final Map<String, String>? cmds; final Map<String, String>? cmds;
@HiveField(4)
final String? preferTempDev; final String? preferTempDev;
@HiveField(5)
final String? logoUrl; final String? logoUrl;
/// The device name of the network interface displayed in the home server card. /// The device name of the network interface displayed in the home server card.
@HiveField(6)
final String? netDev; final String? netDev;
/// The directory where the script is stored. /// The directory where the script is stored.
@HiveField(7)
final String? scriptDir; final String? scriptDir;
const ServerCustom({ const ServerCustom({
@@ -40,8 +35,7 @@ final class ServerCustom {
this.scriptDir, this.scriptDir,
}); });
factory ServerCustom.fromJson(Map<String, dynamic> json) => factory ServerCustom.fromJson(Map<String, dynamic> json) => _$ServerCustomFromJson(json);
_$ServerCustomFromJson(json);
Map<String, dynamic> toJson() => _$ServerCustomToJson(this); Map<String, dynamic> toJson() => _$ServerCustomToJson(this);

View File

@@ -2,85 +2,29 @@
part of 'custom.dart'; part of 'custom.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerCustomAdapter extends TypeAdapter<ServerCustom> {
@override
final int typeId = 7;
@override
ServerCustom read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ServerCustom(
pveAddr: fields[1] as String?,
pveIgnoreCert: fields[2] == null ? false : fields[2] as bool,
cmds: (fields[3] as Map?)?.cast<String, String>(),
preferTempDev: fields[4] as String?,
logoUrl: fields[5] as String?,
netDev: fields[6] as String?,
scriptDir: fields[7] as String?,
);
}
@override
void write(BinaryWriter writer, ServerCustom obj) {
writer
..writeByte(7)
..writeByte(1)
..write(obj.pveAddr)
..writeByte(2)
..write(obj.pveIgnoreCert)
..writeByte(3)
..write(obj.cmds)
..writeByte(4)
..write(obj.preferTempDev)
..writeByte(5)
..write(obj.logoUrl)
..writeByte(6)
..write(obj.netDev)
..writeByte(7)
..write(obj.scriptDir);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerCustomAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom( ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom(
pveAddr: json['pveAddr'] as String?, pveAddr: json['pveAddr'] as String?,
pveIgnoreCert: json['pveIgnoreCert'] as bool? ?? false, pveIgnoreCert: json['pveIgnoreCert'] as bool? ?? false,
cmds: (json['cmds'] as Map<String, dynamic>?)?.map( cmds: (json['cmds'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
), ),
preferTempDev: json['preferTempDev'] as String?, preferTempDev: json['preferTempDev'] as String?,
logoUrl: json['logoUrl'] as String?, logoUrl: json['logoUrl'] as String?,
netDev: json['netDev'] as String?, netDev: json['netDev'] as String?,
scriptDir: json['scriptDir'] as String?, scriptDir: json['scriptDir'] as String?,
); );
Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) => Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
<String, dynamic>{ <String, dynamic>{
'pveAddr': instance.pveAddr, if (instance.pveAddr case final value?) 'pveAddr': value,
'pveIgnoreCert': instance.pveIgnoreCert, 'pveIgnoreCert': instance.pveIgnoreCert,
'cmds': instance.cmds, if (instance.cmds case final value?) 'cmds': value,
'preferTempDev': instance.preferTempDev, if (instance.preferTempDev case final value?) 'preferTempDev': value,
'logoUrl': instance.logoUrl, if (instance.logoUrl case final value?) 'logoUrl': value,
'netDev': instance.netDev, if (instance.netDev case final value?) 'netDev': value,
'scriptDir': instance.scriptDir, if (instance.scriptDir case final value?) 'scriptDir': value,
}; };

View File

@@ -1,29 +1,208 @@
import 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/time_seq.dart'; import 'package:server_box/data/model/server/time_seq.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
class Disk { class Disk with EquatableMixin {
final String fs; final String path;
final String? fsTyp;
final String mount; final String mount;
final int usedPercent; final int usedPercent;
final BigInt used; final BigInt used;
final BigInt size; final BigInt size;
final BigInt avail; final BigInt avail;
/// Device name (e.g., sda1, nvme0n1p1)
final String? name;
/// Internal kernel device name
final String? kname;
/// Filesystem UUID
final String? uuid;
/// Child disks (partitions)
final List<Disk> children;
const Disk({ const Disk({
required this.fs, required this.path,
this.fsTyp,
required this.mount, required this.mount,
required this.usedPercent, required this.usedPercent,
required this.used, required this.used,
required this.size, required this.size,
required this.avail, required this.avail,
this.name,
this.kname,
this.uuid,
this.children = const [],
}); });
static List<Disk> parse(String raw) { static List<Disk> parse(String raw) {
final list = <Disk>[];
raw = raw.trim();
try {
if (raw.startsWith('{')) {
// Parse JSON output from lsblk command
final Map<String, dynamic> jsonData = json.decode(raw);
final List<dynamic> blockdevices = jsonData['blockdevices'] ?? [];
for (final device in blockdevices) {
// Process each device
_processTopLevelDevice(device, list);
}
} else {
// Fallback to the old parsing method in case of non-JSON output
return _parseWithOldMethod(raw);
}
} catch (e) {
Loggers.app.warning('Failed to parse disk info: $e', e);
}
return list;
}
/// Process a top-level device and add all valid disks to the list
static void _processTopLevelDevice(Map<String, dynamic> device, List<Disk> list) {
final disk = _processDiskDevice(device);
if (disk != null) {
list.add(disk);
}
// For devices with children (like physical disks with partitions),
// also process each child individually to ensure BTRFS RAID disks are properly handled
final List<dynamic> childDevices = device['children'] ?? [];
for (final childDevice in childDevices) {
final String childPath = childDevice['path']?.toString() ?? '';
final String childFsType = childDevice['fstype']?.toString() ?? '';
// If this is a BTRFS partition, add it directly to ensure it's properly represented
if (childFsType == 'btrfs' && childPath.isNotEmpty) {
final childDisk = _processSingleDevice(childDevice);
if (childDisk != null) {
list.add(childDisk);
}
}
}
}
/// Process a single device without recursively processing its children
static Disk? _processSingleDevice(Map<String, dynamic> device) {
final fstype = device['fstype']?.toString();
final String mountpoint = device['mountpoint']?.toString() ?? '';
final String path = device['path']?.toString() ?? '';
if (path.isEmpty || (fstype == null && mountpoint.isEmpty)) {
return null;
}
if (!_shouldCalc(fstype ?? '', mountpoint)) {
return null;
}
final sizeStr = device['fssize']?.toString() ?? '0';
final size = (BigInt.tryParse(sizeStr) ?? BigInt.zero) ~/ BigInt.from(1024);
final usedStr = device['fsused']?.toString() ?? '0';
final used = (BigInt.tryParse(usedStr) ?? BigInt.zero) ~/ BigInt.from(1024);
final availStr = device['fsavail']?.toString() ?? '0';
final avail = (BigInt.tryParse(availStr) ?? BigInt.zero) ~/ BigInt.from(1024);
// Parse fsuse% which is usually in the format "45%"
String usePercentStr = device['fsuse%']?.toString() ?? '0';
usePercentStr = usePercentStr.replaceAll('%', '');
final usedPercent = int.tryParse(usePercentStr) ?? 0;
final name = device['name']?.toString();
final kname = device['kname']?.toString();
final uuid = device['uuid']?.toString();
return Disk(
path: path,
fsTyp: fstype,
mount: mountpoint,
usedPercent: usedPercent,
used: used,
size: size,
avail: avail,
name: name,
kname: kname,
uuid: uuid,
children: const [], // No children for direct device
);
}
static Disk? _processDiskDevice(Map<String, dynamic> device) {
final fstype = device['fstype']?.toString();
final String mountpoint = device['mountpoint']?.toString() ?? '';
// For parent devices that don't have a mountpoint themselves
final String path = device['path']?.toString() ?? '';
final String mount = mountpoint;
final List<Disk> childDisks = [];
// Process children devices recursively
final List<dynamic> childDevices = device['children'] ?? [];
for (final childDevice in childDevices) {
final childDisk = _processDiskDevice(childDevice);
if (childDisk != null) {
childDisks.add(childDisk);
}
}
// Handle common filesystem cases or parent devices with children
if ((fstype != null && _shouldCalc(fstype, mount)) ||
(childDisks.isNotEmpty && path.isNotEmpty)) {
final sizeStr = device['fssize']?.toString() ?? '0';
final size = (BigInt.tryParse(sizeStr) ?? BigInt.zero) ~/ BigInt.from(1024);
final usedStr = device['fsused']?.toString() ?? '0';
final used = (BigInt.tryParse(usedStr) ?? BigInt.zero) ~/ BigInt.from(1024);
final availStr = device['fsavail']?.toString() ?? '0';
final avail = (BigInt.tryParse(availStr) ?? BigInt.zero) ~/ BigInt.from(1024);
// Parse fsuse% which is usually in the format "45%"
String usePercentStr = device['fsuse%']?.toString() ?? '0';
usePercentStr = usePercentStr.replaceAll('%', '');
final usedPercent = int.tryParse(usePercentStr) ?? 0;
final name = device['name']?.toString();
final kname = device['kname']?.toString();
final uuid = device['uuid']?.toString();
return Disk(
path: path,
fsTyp: fstype,
mount: mount,
usedPercent: usedPercent,
used: used,
size: size,
avail: avail,
name: name,
kname: kname,
uuid: uuid,
children: childDisks,
);
} else if (childDisks.isNotEmpty) {
// If this is a parent device with no filesystem but has children,
// return the first valid child instead
if (childDisks.isNotEmpty) {
return childDisks.first;
}
}
return null;
}
// Fallback to the old parsing method in case JSON parsing fails
static List<Disk> _parseWithOldMethod(String raw) {
final list = <Disk>[]; final list = <Disk>[];
final items = raw.split('\n'); final items = raw.split('\n');
items.removeAt(0); if (items.isNotEmpty) items.removeAt(0);
var pathCache = ''; var pathCache = '';
for (var item in items) { for (var item in items) {
if (item.isEmpty) { if (item.isEmpty) {
@@ -43,12 +222,12 @@ class Disk {
final mount = vals[5]; final mount = vals[5];
if (!_shouldCalc(fs, mount)) continue; if (!_shouldCalc(fs, mount)) continue;
list.add(Disk( list.add(Disk(
fs: fs, path: fs,
mount: mount, mount: mount,
usedPercent: int.parse(vals[4].replaceFirst('%', '')), usedPercent: int.parse(vals[4].replaceFirst('%', '')),
used: BigInt.parse(vals[2]), used: BigInt.parse(vals[2]) ~/ BigInt.from(1024),
size: BigInt.parse(vals[1]), size: BigInt.parse(vals[1]) ~/ BigInt.from(1024),
avail: BigInt.parse(vals[3]), avail: BigInt.parse(vals[3]) ~/ BigInt.from(1024),
)); ));
} catch (e) { } catch (e) {
continue; continue;
@@ -58,9 +237,8 @@ class Disk {
} }
@override @override
String toString() { List<Object?> get props =>
return 'Disk{dev: $fs, mount: $mount, usedPercent: $usedPercent, used: $used, size: $size, avail: $avail}'; [path, name, kname, fsTyp, mount, usedPercent, used, size, avail, uuid, children];
}
} }
class DiskIO extends TimeSeq<List<DiskIOPiece>> { class DiskIO extends TimeSeq<List<DiskIOPiece>> {
@@ -72,9 +250,16 @@ class DiskIO extends TimeSeq<List<DiskIOPiece>> {
} }
(double?, double?) _getSpeed(String dev) { (double?, double?) _getSpeed(String dev) {
if (dev.startsWith('/dev/')) dev = dev.substring(5); // Extract the device name from path if needed
final old = pre.firstWhereOrNull((e) => e.dev == dev); String searchDev = dev;
final new_ = now.firstWhereOrNull((e) => e.dev == dev); if (dev.startsWith('/dev/')) {
searchDev = dev.substring(5);
}
// Try to find by exact device name first
final old = pre.firstWhereOrNull((e) => e.dev == searchDev);
final new_ = now.firstWhereOrNull((e) => e.dev == searchDev);
if (old == null || new_ == null) return (null, null); if (old == null || new_ == null) return (null, null);
final sectorsRead = new_.sectorsRead - old.sectorsRead; final sectorsRead = new_.sectorsRead - old.sectorsRead;
final sectorsWrite = new_.sectorsWrite - old.sectorsWrite; final sectorsWrite = new_.sectorsWrite - old.sectorsWrite;
@@ -104,11 +289,14 @@ class DiskIO extends TimeSeq<List<DiskIOPiece>> {
!item.dev.startsWith('vd') && !item.dev.startsWith('vd') &&
!item.dev.startsWith('hd') && !item.dev.startsWith('hd') &&
!item.dev.startsWith('mmcblk') && !item.dev.startsWith('mmcblk') &&
!item.dev.startsWith('sr')) continue; !item.dev.startsWith('sr')) {
continue;
}
final (read_, write_) = _getSpeed(item.dev); final (read_, write_) = _getSpeed(item.dev);
read += read_ ?? 0; read += read_ ?? 0;
write += write_ ?? 0; write += write_ ?? 0;
} }
final readStr = '${read.bytes2Str}/s'; final readStr = '${read.bytes2Str}/s';
final writeStr = '${write.bytes2Str}/s'; final writeStr = '${write.bytes2Str}/s';
return (readStr, writeStr); return (readStr, writeStr);
@@ -166,7 +354,11 @@ class DiskUsage {
required this.size, required this.size,
}); });
double get usedPercent => used / size * 100; double get usedPercent {
// Avoid division by zero
if (size == BigInt.zero) return 0;
return used / size * 100;
}
/// Find all devs, add their used and size /// Find all devs, add their used and size
static DiskUsage parse(List<Disk> disks) { static DiskUsage parse(List<Disk> disks) {
@@ -174,9 +366,12 @@ class DiskUsage {
var used = BigInt.zero; var used = BigInt.zero;
var size = BigInt.zero; var size = BigInt.zero;
for (var disk in disks) { for (var disk in disks) {
if (!_shouldCalc(disk.fs, disk.mount)) continue; if (!_shouldCalc(disk.path, disk.mount)) continue;
if (devs.contains(disk.fs)) continue; // Use a combination of path and kernel name to uniquely identify disks
devs.add(disk.fs); // This helps distinguish between multiple physical disks in BTRFS RAID setups
final uniqueId = '${disk.path}:${disk.kname ?? "unknown"}';
if (devs.contains(uniqueId)) continue;
devs.add(uniqueId);
used += disk.used; used += disk.used;
size += disk.size; size += disk.size;
} }
@@ -185,12 +380,24 @@ class DiskUsage {
} }
bool _shouldCalc(String fs, String mount) { bool _shouldCalc(String fs, String mount) {
// Skip swap partitions
// if (mount == '[SWAP]') return false;
// Include standard filesystems
if (fs.startsWith('/dev')) return true; if (fs.startsWith('/dev')) return true;
// Some NAS may have mounted path like this `//192.168.1.2/` // Some NAS may have mounted path like this `//192.168.1.2/`
if (fs.startsWith('//')) return true; if (fs.startsWith('//')) return true;
if (mount.startsWith('/mnt')) return true; if (mount.startsWith('/mnt')) return true;
// if (fs.startsWith('shm') ||
// fs.startsWith('overlay') || // Include common filesystem types
// fs.startsWith('tmpfs')) return false; // final commonFsTypes = ['ext2', 'ext3', 'ext4', 'xfs', 'btrfs', 'zfs', 'ntfs', 'fat', 'vfat'];
return false; // if (commonFsTypes.any((type) => fs.toLowerCase() == type)) return true;
// Skip special filesystems
// if (fs == 'LVM2_member' || fs == 'crypto_LUKS') return false;
if (fs.startsWith('shm') || fs.startsWith('overlay') || fs.startsWith('tmpfs')) {
return false;
}
return true;
} }

View File

@@ -0,0 +1,204 @@
import 'dart:convert';
import 'package:fl_lib/fl_lib.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'disk_smart.freezed.dart';
part 'disk_smart.g.dart';
@freezed
class DiskSmart with _$DiskSmart {
const DiskSmart._();
const factory DiskSmart({
required String device,
bool? healthy,
double? temperature,
String? model,
String? serial,
int? powerOnHours,
int? powerCycleCount,
required Map<String, dynamic> rawData,
required Map<String, SmartAttribute> smartAttributes,
}) = _DiskSmart;
factory DiskSmart.fromJson(Map<String, dynamic> json) => _$DiskSmartFromJson(json);
static List<DiskSmart> parse(String raw) {
final results = <DiskSmart>[];
final jsonBlocks = raw.split('\n\n').where((s) => s.trim().isNotEmpty);
for (final jsonStr in jsonBlocks) {
try {
final data = json.decode(jsonStr.trim()) as Map<String, dynamic>;
// Basic
final device = data['device']?['name']?.toString() ?? '';
final healthy = data['smart_status']?['passed'] as bool?;
// Model and Serial
final model =
data['model_name']?.toString() ??
data['model_family']?.toString() ??
data['device']?['model_name']?.toString();
final serial = data['serial_number']?.toString() ?? data['device']?['serial_number']?.toString();
// SMART Attrs
final smartAttributes = _parseSmartAttributes(data);
final temperature = _extractTemperature(data, smartAttributes);
final powerOnHours =
data['power_on_time']?['hours'] as int? ?? smartAttributes['Power_On_Hours']?.rawValue as int?;
final powerCycleCount =
data['power_cycle_count'] as int? ?? smartAttributes['Power_Cycle_Count']?.rawValue as int?;
results.add(
DiskSmart(
device: device,
healthy: healthy,
temperature: temperature,
model: model,
serial: serial,
powerOnHours: powerOnHours,
powerCycleCount: powerCycleCount,
rawData: data,
smartAttributes: smartAttributes,
),
);
} catch (e, s) {
Loggers.app.warning('DiskSmart parse', e, s);
}
}
return results;
}
static Map<String, SmartAttribute> _parseSmartAttributes(Map<String, dynamic> data) {
final attributes = <String, SmartAttribute>{};
final attrTable = data['ata_smart_attributes']?['table'] as List?;
if (attrTable == null) return attributes;
for (final attr in attrTable) {
if (attr is Map<String, dynamic>) {
final name = attr['name']?.toString();
if (name != null) {
attributes[name] = SmartAttribute(
id: attr['id'] as int?,
name: name,
value: attr['value'] as int?,
worst: attr['worst'] as int?,
thresh: attr['thresh'] as int?,
whenFailed: attr['when_failed']?.toString(),
rawValue: attr['raw']?['value'],
rawString: attr['raw']?['string']?.toString(),
flags: SmartAttributeFlags.fromMap(attr['flags'] as Map<String, dynamic>? ?? {}),
);
}
}
}
return attributes;
}
static final _tempReg = RegExp(r'^(\d+(?:\.\d+)?)');
/// Extract temperature from the data
static double? _extractTemperature(Map<String, dynamic> data, Map<String, SmartAttribute> attrs) {
// Directly
final directTemp = data['temperature']?['current'];
if (directTemp is num) return directTemp.toDouble();
// SMART attribute
final tempAttr = attrs['Temperature_Celsius'];
if (tempAttr != null) {
// "35 (Min/Max 14/61)"
final rawString = tempAttr.rawString;
if (rawString != null) {
final match = _tempReg.firstMatch(rawString);
if (match != null) {
return double.tryParse(match.group(1)!);
}
}
// Simple numeric value
if (tempAttr.rawValue is num && tempAttr.rawValue! < 150) {
return tempAttr.rawValue!.toDouble();
}
}
return null;
}
/// Get the specific SMART attribute by name
SmartAttribute? getAttribute(String name) => smartAttributes[name];
int? get ssdLifeLeft => smartAttributes['SSD_Life_Left']?.rawValue as int?;
int? get lifetimeWritesGiB => smartAttributes['Lifetime_Writes_GiB']?.rawValue as int?;
int? get lifetimeReadsGiB => smartAttributes['Lifetime_Reads_GiB']?.rawValue as int?;
int? get unsafeShutdownCount => smartAttributes['Unsafe_Shutdown_Count']?.rawValue as int?;
int? get averageEraseCount => smartAttributes['Average_Erase_Count']?.rawValue as int?;
int? get maxEraseCount => smartAttributes['Max_Erase_Count']?.rawValue as int?;
@override
String toString() => 'DiskSmart($device)';
}
@freezed
class SmartAttribute with _$SmartAttribute {
const SmartAttribute._();
const factory SmartAttribute({
int? id,
required String name,
int? value,
int? worst,
int? thresh,
String? whenFailed,
dynamic rawValue,
String? rawString,
required SmartAttributeFlags flags,
}) = _SmartAttribute;
factory SmartAttribute.fromJson(Map<String, dynamic> json) => _$SmartAttributeFromJson(json);
@override
String toString() {
return 'SmartAttribute(id: $id, name: $name)';
}
}
@freezed
class SmartAttributeFlags with _$SmartAttributeFlags {
const SmartAttributeFlags._();
const factory SmartAttributeFlags({
int? value,
String? string,
@Default(false) bool prefailure,
@Default(false) bool updatedOnline,
@Default(false) bool performance,
@Default(false) bool errorRate,
@Default(false) bool eventCount,
@Default(false) bool autoKeep,
}) = _SmartAttributeFlags;
factory SmartAttributeFlags.fromJson(Map<String, dynamic> json) => _$SmartAttributeFlagsFromJson(json);
factory SmartAttributeFlags.fromMap(Map<String, dynamic> map) {
return SmartAttributeFlags(
value: map['value'] as int?,
string: map['string']?.toString(),
prefailure: map['prefailure'] == true,
updatedOnline: map['updated_online'] == true,
performance: map['performance'] == true,
errorRate: map['error_rate'] == true,
eventCount: map['event_count'] == true,
autoKeep: map['auto_keep'] == true,
);
}
@override
String toString() {
return 'SmartAttributeFlags(value: $value, string: $string)';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'disk_smart.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$DiskSmartImpl _$$DiskSmartImplFromJson(Map<String, dynamic> json) =>
_$DiskSmartImpl(
device: json['device'] as String,
healthy: json['healthy'] as bool?,
temperature: (json['temperature'] as num?)?.toDouble(),
model: json['model'] as String?,
serial: json['serial'] as String?,
powerOnHours: (json['powerOnHours'] as num?)?.toInt(),
powerCycleCount: (json['powerCycleCount'] as num?)?.toInt(),
rawData: json['rawData'] as Map<String, dynamic>,
smartAttributes: (json['smartAttributes'] as Map<String, dynamic>).map(
(k, e) =>
MapEntry(k, SmartAttribute.fromJson(e as Map<String, dynamic>)),
),
);
Map<String, dynamic> _$$DiskSmartImplToJson(_$DiskSmartImpl instance) =>
<String, dynamic>{
'device': instance.device,
'healthy': instance.healthy,
'temperature': instance.temperature,
'model': instance.model,
'serial': instance.serial,
'powerOnHours': instance.powerOnHours,
'powerCycleCount': instance.powerCycleCount,
'rawData': instance.rawData,
'smartAttributes': instance.smartAttributes,
};
_$SmartAttributeImpl _$$SmartAttributeImplFromJson(Map<String, dynamic> json) =>
_$SmartAttributeImpl(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String,
value: (json['value'] as num?)?.toInt(),
worst: (json['worst'] as num?)?.toInt(),
thresh: (json['thresh'] as num?)?.toInt(),
whenFailed: json['whenFailed'] as String?,
rawValue: json['rawValue'],
rawString: json['rawString'] as String?,
flags: SmartAttributeFlags.fromJson(
json['flags'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$$SmartAttributeImplToJson(
_$SmartAttributeImpl instance,
) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'value': instance.value,
'worst': instance.worst,
'thresh': instance.thresh,
'whenFailed': instance.whenFailed,
'rawValue': instance.rawValue,
'rawString': instance.rawString,
'flags': instance.flags,
};
_$SmartAttributeFlagsImpl _$$SmartAttributeFlagsImplFromJson(
Map<String, dynamic> json,
) => _$SmartAttributeFlagsImpl(
value: (json['value'] as num?)?.toInt(),
string: json['string'] as String?,
prefailure: json['prefailure'] as bool? ?? false,
updatedOnline: json['updatedOnline'] as bool? ?? false,
performance: json['performance'] as bool? ?? false,
errorRate: json['errorRate'] as bool? ?? false,
eventCount: json['eventCount'] as bool? ?? false,
autoKeep: json['autoKeep'] as bool? ?? false,
);
Map<String, dynamic> _$$SmartAttributeFlagsImplToJson(
_$SmartAttributeFlagsImpl instance,
) => <String, dynamic>{
'value': instance.value,
'string': instance.string,
'prefailure': instance.prefailure,
'updatedOnline': instance.updatedOnline,
'performance': instance.performance,
'errorRate': instance.errorRate,
'eventCount': instance.eventCount,
'autoKeep': instance.autoKeep,
};

View File

@@ -11,6 +11,7 @@ enum Dist {
alpine, alpine,
rocky, rocky,
deepin, deepin,
coreelec,
; ;
} }

View File

@@ -1,3 +1,5 @@
// ignore_for_file: unintended_html_in_doc_comment
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/time_seq.dart'; import 'package:server_box/data/model/server/time_seq.dart';

View File

@@ -1,15 +1,11 @@
import 'package:hive_flutter/hive_flutter.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'private_key_info.g.dart'; part 'private_key_info.g.dart';
@JsonSerializable() @JsonSerializable()
@HiveType(typeId: 1)
class PrivateKeyInfo { class PrivateKeyInfo {
@HiveField(0)
final String id; final String id;
@JsonKey(name: 'private_key') @JsonKey(name: 'private_key')
@HiveField(1)
final String key; final String key;
const PrivateKeyInfo({ const PrivateKeyInfo({
@@ -17,8 +13,7 @@ class PrivateKeyInfo {
required this.key, required this.key,
}); });
factory PrivateKeyInfo.fromJson(Map<String, dynamic> json) => factory PrivateKeyInfo.fromJson(Map<String, dynamic> json) => _$PrivateKeyInfoFromJson(json);
_$PrivateKeyInfoFromJson(json);
Map<String, dynamic> toJson() => _$PrivateKeyInfoToJson(this); Map<String, dynamic> toJson() => _$PrivateKeyInfoToJson(this);

View File

@@ -2,47 +2,6 @@
part of 'private_key_info.dart'; part of 'private_key_info.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PrivateKeyInfoAdapter extends TypeAdapter<PrivateKeyInfo> {
@override
final int typeId = 1;
@override
PrivateKeyInfo read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return PrivateKeyInfo(
id: fields[0] as String,
key: fields[1] as String,
);
}
@override
void write(BinaryWriter writer, PrivateKeyInfo obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.key);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PrivateKeyInfoAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
@@ -54,7 +13,4 @@ PrivateKeyInfo _$PrivateKeyInfoFromJson(Map<String, dynamic> json) =>
); );
Map<String, dynamic> _$PrivateKeyInfoToJson(PrivateKeyInfo instance) => Map<String, dynamic> _$PrivateKeyInfoToJson(PrivateKeyInfo instance) =>
<String, dynamic>{ <String, dynamic>{'id': instance.id, 'private_key': instance.key};
'id': instance.id,
'private_key': instance.key,
};

View File

@@ -1,10 +1,11 @@
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:server_box/data/model/app/error.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/battery.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/conn.dart';
import 'package:server_box/data/model/server/cpu.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/disk.dart';
import 'package:server_box/data/model/server/disk_smart.dart';
import 'package:server_box/data/model/server/memory.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/net_speed.dart';
import 'package:server_box/data/model/server/nvdia.dart'; import 'package:server_box/data/model/server/nvdia.dart';
@@ -19,12 +20,7 @@ class Server {
SSHClient? client; SSHClient? client;
ServerConn conn; ServerConn conn;
Server( Server(this.spi, this.status, this.conn, {this.client});
this.spi,
this.status,
this.conn, {
this.client,
});
bool get needGenClient => conn < ServerConn.connecting; bool get needGenClient => conn < ServerConn.connecting;
@@ -44,6 +40,7 @@ class ServerStatus {
SystemType system; SystemType system;
Err? err; Err? err;
DiskIO diskIO; DiskIO diskIO;
List<DiskSmart> diskSmart;
List<NvidiaSmiItem>? nvidia; List<NvidiaSmiItem>? nvidia;
final List<Battery> batteries = []; final List<Battery> batteries = [];
final Map<StatusCmdType, String> more = {}; final Map<StatusCmdType, String> more = {};
@@ -61,6 +58,7 @@ class ServerStatus {
required this.temps, required this.temps,
required this.system, required this.system,
required this.diskIO, required this.diskIO,
this.diskSmart = const [],
this.err, this.err,
this.nvidia, this.nvidia,
this.diskUsage, this.diskUsage,

View File

@@ -1,15 +1,16 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:server_box/data/model/server/server.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/model/server/wol_cfg.dart';
import 'package:server_box/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/store/server.dart';
import 'package:server_box/data/model/app/error.dart'; part 'server_private_info.freezed.dart';
part 'server_private_info.g.dart'; part 'server_private_info.g.dart';
/// In the first version, it's called `ServerPrivateInfo` which was designed to /// In the first version, it's called `ServerPrivateInfo` which was designed to
@@ -18,79 +19,75 @@ part 'server_private_info.g.dart';
/// Some params named as `spi` in the codebase which is the abbreviation of `ServerPrivateInfo`. /// Some params named as `spi` in the codebase which is the abbreviation of `ServerPrivateInfo`.
/// ///
/// Nowaday, more fields are added to this class, and it's renamed to `Spi`. /// Nowaday, more fields are added to this class, and it's renamed to `Spi`.
@JsonSerializable() @Freezed(fromJson: false)
@HiveType(typeId: 3) @JsonSerializable(includeIfNull: false)
class Spi { class Spi with _$Spi {
@HiveField(0) const Spi._();
final String name;
@HiveField(1)
final String ip;
@HiveField(2)
final int port;
@HiveField(3)
final String user;
@HiveField(4)
final String? pwd;
/// [id] of private key const factory Spi({
@JsonKey(name: 'pubKeyId') required String name,
@HiveField(5) required String ip,
final String? keyId; required int port,
@HiveField(6) required String user,
final List<String>? tags; String? pwd,
@HiveField(7)
final String? alterUrl;
@HiveField(8, defaultValue: true)
final bool autoConnect;
/// [id] of the jump server /// [id] of private key
@HiveField(9) @JsonKey(name: 'pubKeyId') String? keyId,
final String? jumpId; List<String>? tags,
String? alterUrl,
@Default(true) @JsonKey(defaultValue: true) bool autoConnect,
@HiveField(10) /// [id] of the jump server
final ServerCustom? custom; String? jumpId,
ServerCustom? custom,
WakeOnLanCfg? wolCfg,
@HiveField(11) /// It only applies to SSH terminal.
final WakeOnLanCfg? wolCfg; Map<String, String>? envs,
@JsonKey(fromJson: Spi.parseId) @HiveField(13, defaultValue: '') required String id,
/// It only applies to SSH terminal. }) = _Spi;
@HiveField(12)
final Map<String, String>? envs;
final String id;
const Spi({
required this.name,
required this.ip,
required this.port,
required this.user,
required this.pwd,
this.keyId,
this.tags,
this.alterUrl,
this.autoConnect = true,
this.jumpId,
this.custom,
this.wolCfg,
this.envs,
}) : id = '$user@$ip:$port';
factory Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json); factory Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
Map<String, dynamic> toJson() => _$SpiToJson(this);
@override @override
String toString() => id; String toString() => 'Spi<$oldId>';
static String parseId(Object? id) {
if (id == null || id is! String || id.isEmpty) return ShortId.generate();
return id;
}
} }
extension Spix on Spi { extension Spix on Spi {
/// After upgrading to >= 1155, this field is only recommended to be used
/// for displaying the server name.
String get oldId => '$user@$ip:$port';
/// Save the [Spi] to the local storage.
void save() => ServerStore.instance.put(this);
/// Migrate the [oldId] to the new generated [id] by [ShortId.generate].
///
/// Returns:
/// - `null` if the [id] is not empty.
/// - The new [id] if the [id] is empty.
String? migrateId() {
if (id.isNotEmpty) return null;
ServerStore.instance.delete(oldId);
final newSpi = copyWith(id: ShortId.generate());
newSpi.save();
return newSpi.id;
}
String toJsonString() => json.encode(toJson()); String toJsonString() => json.encode(toJson());
VNode<Server>? get server => ServerProvider.pick(spi: this); VNode<Server>? get server => ServerProvider.pick(spi: this);
VNode<Server>? get jumpServer => ServerProvider.pick(id: jumpId); VNode<Server>? get jumpServer => ServerProvider.pick(id: jumpId);
bool shouldReconnect(Spi old) { bool shouldReconnect(Spi old) {
return id != old.id || return user != old.user ||
ip != old.ip ||
port != old.port ||
pwd != old.pwd || pwd != old.pwd ||
keyId != old.keyId || keyId != old.keyId ||
alterUrl != old.alterUrl || alterUrl != old.alterUrl ||
@@ -122,27 +119,27 @@ extension Spix on Spi {
/// Just for showing the struct of the class. /// Just for showing the struct of the class.
/// ///
/// **NOT** the default value. /// **NOT** the default value.
static const example = Spi( static final example = Spi(
name: 'name', name: 'name',
ip: 'ip', ip: 'ip',
port: 22, port: 22,
user: 'root', user: 'root',
pwd: 'pwd', pwd: 'pwd',
keyId: 'private_key_id', keyId: 'private_key_id',
tags: ['tag1', 'tag2'], tags: ['tag1', 'tag2'],
alterUrl: 'user@ip:port', alterUrl: 'user@ip:port',
autoConnect: true, autoConnect: true,
jumpId: 'jump_server_id', jumpId: 'jump_server_id',
custom: ServerCustom( custom: ServerCustom(
pveAddr: 'http://localhost:8006', pveAddr: 'http://localhost:8006',
pveIgnoreCert: false, pveIgnoreCert: false,
cmds: { cmds: {
'echo': 'echo hello', 'echo': 'echo hello',
}, },
preferTempDev: 'nvme-pci-0400', preferTempDev: 'nvme-pci-0400',
logoUrl: 'https://example.com/logo.png', logoUrl: 'https://example.com/logo.png',
), ),
); id: 'id');
bool get isRoot => user == 'root'; bool get isRoot => user == 'root';
} }

View File

@@ -0,0 +1,487 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'server_private_info.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$Spi {
String get name => throw _privateConstructorUsedError;
String get ip => throw _privateConstructorUsedError;
int get port => throw _privateConstructorUsedError;
String get user => throw _privateConstructorUsedError;
String? get pwd => throw _privateConstructorUsedError;
/// [id] of private key
@JsonKey(name: 'pubKeyId')
String? get keyId => throw _privateConstructorUsedError;
List<String>? get tags => throw _privateConstructorUsedError;
String? get alterUrl => throw _privateConstructorUsedError;
@JsonKey(defaultValue: true)
bool get autoConnect => throw _privateConstructorUsedError;
/// [id] of the jump server
String? get jumpId => throw _privateConstructorUsedError;
ServerCustom? get custom => throw _privateConstructorUsedError;
WakeOnLanCfg? get wolCfg => throw _privateConstructorUsedError;
/// It only applies to SSH terminal.
Map<String, String>? get envs => throw _privateConstructorUsedError;
@JsonKey(fromJson: Spi.parseId)
@HiveField(13, defaultValue: '')
String get id => throw _privateConstructorUsedError;
/// Serializes this Spi to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SpiCopyWith<Spi> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SpiCopyWith<$Res> {
factory $SpiCopyWith(Spi value, $Res Function(Spi) then) =
_$SpiCopyWithImpl<$Res, Spi>;
@useResult
$Res call({
String name,
String ip,
int port,
String user,
String? pwd,
@JsonKey(name: 'pubKeyId') String? keyId,
List<String>? tags,
String? alterUrl,
@JsonKey(defaultValue: true) bool autoConnect,
String? jumpId,
ServerCustom? custom,
WakeOnLanCfg? wolCfg,
Map<String, String>? envs,
@JsonKey(fromJson: Spi.parseId) @HiveField(13, defaultValue: '') String id,
});
}
/// @nodoc
class _$SpiCopyWithImpl<$Res, $Val extends Spi> implements $SpiCopyWith<$Res> {
_$SpiCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? ip = null,
Object? port = null,
Object? user = null,
Object? pwd = freezed,
Object? keyId = freezed,
Object? tags = freezed,
Object? alterUrl = freezed,
Object? autoConnect = null,
Object? jumpId = freezed,
Object? custom = freezed,
Object? wolCfg = freezed,
Object? envs = freezed,
Object? id = null,
}) {
return _then(
_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
ip: null == ip
? _value.ip
: ip // ignore: cast_nullable_to_non_nullable
as String,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
user: null == user
? _value.user
: user // ignore: cast_nullable_to_non_nullable
as String,
pwd: freezed == pwd
? _value.pwd
: pwd // ignore: cast_nullable_to_non_nullable
as String?,
keyId: freezed == keyId
? _value.keyId
: keyId // ignore: cast_nullable_to_non_nullable
as String?,
tags: freezed == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>?,
alterUrl: freezed == alterUrl
? _value.alterUrl
: alterUrl // ignore: cast_nullable_to_non_nullable
as String?,
autoConnect: null == autoConnect
? _value.autoConnect
: autoConnect // ignore: cast_nullable_to_non_nullable
as bool,
jumpId: freezed == jumpId
? _value.jumpId
: jumpId // ignore: cast_nullable_to_non_nullable
as String?,
custom: freezed == custom
? _value.custom
: custom // ignore: cast_nullable_to_non_nullable
as ServerCustom?,
wolCfg: freezed == wolCfg
? _value.wolCfg
: wolCfg // ignore: cast_nullable_to_non_nullable
as WakeOnLanCfg?,
envs: freezed == envs
? _value.envs
: envs // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$SpiImplCopyWith<$Res> implements $SpiCopyWith<$Res> {
factory _$$SpiImplCopyWith(_$SpiImpl value, $Res Function(_$SpiImpl) then) =
__$$SpiImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String name,
String ip,
int port,
String user,
String? pwd,
@JsonKey(name: 'pubKeyId') String? keyId,
List<String>? tags,
String? alterUrl,
@JsonKey(defaultValue: true) bool autoConnect,
String? jumpId,
ServerCustom? custom,
WakeOnLanCfg? wolCfg,
Map<String, String>? envs,
@JsonKey(fromJson: Spi.parseId) @HiveField(13, defaultValue: '') String id,
});
}
/// @nodoc
class __$$SpiImplCopyWithImpl<$Res> extends _$SpiCopyWithImpl<$Res, _$SpiImpl>
implements _$$SpiImplCopyWith<$Res> {
__$$SpiImplCopyWithImpl(_$SpiImpl _value, $Res Function(_$SpiImpl) _then)
: super(_value, _then);
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? ip = null,
Object? port = null,
Object? user = null,
Object? pwd = freezed,
Object? keyId = freezed,
Object? tags = freezed,
Object? alterUrl = freezed,
Object? autoConnect = null,
Object? jumpId = freezed,
Object? custom = freezed,
Object? wolCfg = freezed,
Object? envs = freezed,
Object? id = null,
}) {
return _then(
_$SpiImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
ip: null == ip
? _value.ip
: ip // ignore: cast_nullable_to_non_nullable
as String,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
user: null == user
? _value.user
: user // ignore: cast_nullable_to_non_nullable
as String,
pwd: freezed == pwd
? _value.pwd
: pwd // ignore: cast_nullable_to_non_nullable
as String?,
keyId: freezed == keyId
? _value.keyId
: keyId // ignore: cast_nullable_to_non_nullable
as String?,
tags: freezed == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>?,
alterUrl: freezed == alterUrl
? _value.alterUrl
: alterUrl // ignore: cast_nullable_to_non_nullable
as String?,
autoConnect: null == autoConnect
? _value.autoConnect
: autoConnect // ignore: cast_nullable_to_non_nullable
as bool,
jumpId: freezed == jumpId
? _value.jumpId
: jumpId // ignore: cast_nullable_to_non_nullable
as String?,
custom: freezed == custom
? _value.custom
: custom // ignore: cast_nullable_to_non_nullable
as ServerCustom?,
wolCfg: freezed == wolCfg
? _value.wolCfg
: wolCfg // ignore: cast_nullable_to_non_nullable
as WakeOnLanCfg?,
envs: freezed == envs
? _value._envs
: envs // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
@JsonSerializable(createFactory: false)
class _$SpiImpl extends _Spi {
const _$SpiImpl({
required this.name,
required this.ip,
required this.port,
required this.user,
this.pwd,
@JsonKey(name: 'pubKeyId') this.keyId,
final List<String>? tags,
this.alterUrl,
@JsonKey(defaultValue: true) this.autoConnect = true,
this.jumpId,
this.custom,
this.wolCfg,
final Map<String, String>? envs,
@JsonKey(fromJson: Spi.parseId)
@HiveField(13, defaultValue: '')
required this.id,
}) : _tags = tags,
_envs = envs,
super._();
@override
final String name;
@override
final String ip;
@override
final int port;
@override
final String user;
@override
final String? pwd;
/// [id] of private key
@override
@JsonKey(name: 'pubKeyId')
final String? keyId;
final List<String>? _tags;
@override
List<String>? get tags {
final value = _tags;
if (value == null) return null;
if (_tags is EqualUnmodifiableListView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final String? alterUrl;
@override
@JsonKey(defaultValue: true)
final bool autoConnect;
/// [id] of the jump server
@override
final String? jumpId;
@override
final ServerCustom? custom;
@override
final WakeOnLanCfg? wolCfg;
/// It only applies to SSH terminal.
final Map<String, String>? _envs;
/// It only applies to SSH terminal.
@override
Map<String, String>? get envs {
final value = _envs;
if (value == null) return null;
if (_envs is EqualUnmodifiableMapView) return _envs;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
@JsonKey(fromJson: Spi.parseId)
@HiveField(13, defaultValue: '')
final String id;
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SpiImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.ip, ip) || other.ip == ip) &&
(identical(other.port, port) || other.port == port) &&
(identical(other.user, user) || other.user == user) &&
(identical(other.pwd, pwd) || other.pwd == pwd) &&
(identical(other.keyId, keyId) || other.keyId == keyId) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
(identical(other.alterUrl, alterUrl) ||
other.alterUrl == alterUrl) &&
(identical(other.autoConnect, autoConnect) ||
other.autoConnect == autoConnect) &&
(identical(other.jumpId, jumpId) || other.jumpId == jumpId) &&
(identical(other.custom, custom) || other.custom == custom) &&
(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg) &&
const DeepCollectionEquality().equals(other._envs, _envs) &&
(identical(other.id, id) || other.id == id));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
name,
ip,
port,
user,
pwd,
keyId,
const DeepCollectionEquality().hash(_tags),
alterUrl,
autoConnect,
jumpId,
custom,
wolCfg,
const DeepCollectionEquality().hash(_envs),
id,
);
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SpiImplCopyWith<_$SpiImpl> get copyWith =>
__$$SpiImplCopyWithImpl<_$SpiImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SpiImplToJson(this);
}
}
abstract class _Spi extends Spi {
const factory _Spi({
required final String name,
required final String ip,
required final int port,
required final String user,
final String? pwd,
@JsonKey(name: 'pubKeyId') final String? keyId,
final List<String>? tags,
final String? alterUrl,
@JsonKey(defaultValue: true) final bool autoConnect,
final String? jumpId,
final ServerCustom? custom,
final WakeOnLanCfg? wolCfg,
final Map<String, String>? envs,
@JsonKey(fromJson: Spi.parseId)
@HiveField(13, defaultValue: '')
required final String id,
}) = _$SpiImpl;
const _Spi._() : super._();
@override
String get name;
@override
String get ip;
@override
int get port;
@override
String get user;
@override
String? get pwd;
/// [id] of private key
@override
@JsonKey(name: 'pubKeyId')
String? get keyId;
@override
List<String>? get tags;
@override
String? get alterUrl;
@override
@JsonKey(defaultValue: true)
bool get autoConnect;
/// [id] of the jump server
@override
String? get jumpId;
@override
ServerCustom? get custom;
@override
WakeOnLanCfg? get wolCfg;
/// It only applies to SSH terminal.
@override
Map<String, String>? get envs;
@override
@JsonKey(fromJson: Spi.parseId)
@HiveField(13, defaultValue: '')
String get id;
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SpiImplCopyWith<_$SpiImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -2,118 +2,63 @@
part of 'server_private_info.dart'; part of 'server_private_info.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class SpiAdapter extends TypeAdapter<Spi> {
@override
final int typeId = 3;
@override
Spi read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Spi(
name: fields[0] as String,
ip: fields[1] as String,
port: fields[2] as int,
user: fields[3] as String,
pwd: fields[4] as String?,
keyId: fields[5] as String?,
tags: (fields[6] as List?)?.cast<String>(),
alterUrl: fields[7] as String?,
autoConnect: fields[8] == null ? true : fields[8] as bool,
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, Spi obj) {
writer
..writeByte(13)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.ip)
..writeByte(2)
..write(obj.port)
..writeByte(3)
..write(obj.user)
..writeByte(4)
..write(obj.pwd)
..writeByte(5)
..write(obj.keyId)
..writeByte(6)
..write(obj.tags)
..writeByte(7)
..write(obj.alterUrl)
..writeByte(8)
..write(obj.autoConnect)
..writeByte(9)
..write(obj.jumpId)
..writeByte(10)
..write(obj.custom)
..writeByte(11)
..write(obj.wolCfg)
..writeByte(12)
..write(obj.envs);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SpiAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
Spi _$SpiFromJson(Map<String, dynamic> json) => Spi( Spi _$SpiFromJson(Map<String, dynamic> json) => Spi(
name: json['name'] as String, name: json['name'] as String,
ip: json['ip'] as String, ip: json['ip'] as String,
port: (json['port'] as num).toInt(), port: (json['port'] as num).toInt(),
user: json['user'] as String, user: json['user'] as String,
pwd: json['pwd'] as String?, pwd: json['pwd'] as String?,
keyId: json['pubKeyId'] as String?, keyId: json['pubKeyId'] as String?,
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(), tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
alterUrl: json['alterUrl'] as String?, alterUrl: json['alterUrl'] as String?,
autoConnect: json['autoConnect'] as bool? ?? true, autoConnect: json['autoConnect'] as bool? ?? true,
jumpId: json['jumpId'] as String?, jumpId: json['jumpId'] as String?,
custom: json['custom'] == null custom: json['custom'] == null
? null ? null
: ServerCustom.fromJson(json['custom'] as Map<String, dynamic>), : ServerCustom.fromJson(json['custom'] as Map<String, dynamic>),
wolCfg: json['wolCfg'] == null wolCfg: json['wolCfg'] == null
? null ? null
: WakeOnLanCfg.fromJson(json['wolCfg'] as Map<String, dynamic>), : WakeOnLanCfg.fromJson(json['wolCfg'] as Map<String, dynamic>),
envs: (json['envs'] as Map<String, dynamic>?)?.map( envs: (json['envs'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
), ),
); id: Spi.parseId(json['id']),
);
Map<String, dynamic> _$SpiToJson(Spi instance) => <String, dynamic>{ Map<String, dynamic> _$SpiToJson(Spi instance) => <String, dynamic>{
'name': instance.name, 'name': instance.name,
'ip': instance.ip, 'ip': instance.ip,
'port': instance.port, 'port': instance.port,
'user': instance.user, 'user': instance.user,
'pwd': instance.pwd, if (instance.pwd case final value?) 'pwd': value,
'pubKeyId': instance.keyId, if (instance.keyId case final value?) 'pubKeyId': value,
'tags': instance.tags, if (instance.tags case final value?) 'tags': value,
'alterUrl': instance.alterUrl, if (instance.alterUrl case final value?) 'alterUrl': value,
'autoConnect': instance.autoConnect, 'autoConnect': instance.autoConnect,
'jumpId': instance.jumpId, if (instance.jumpId case final value?) 'jumpId': value,
'custom': instance.custom, if (instance.custom case final value?) 'custom': value,
'wolCfg': instance.wolCfg, if (instance.wolCfg case final value?) 'wolCfg': value,
'envs': instance.envs, if (instance.envs case final value?) 'envs': value,
}; 'id': instance.id,
};
Map<String, dynamic> _$$SpiImplToJson(_$SpiImpl instance) => <String, dynamic>{
'name': instance.name,
'ip': instance.ip,
'port': instance.port,
'user': instance.user,
'pwd': instance.pwd,
'pubKeyId': instance.keyId,
'tags': instance.tags,
'alterUrl': instance.alterUrl,
'autoConnect': instance.autoConnect,
'jumpId': instance.jumpId,
'custom': instance.custom,
'wolCfg': instance.wolCfg,
'envs': instance.envs,
'id': instance.id,
};

View File

@@ -1,17 +1,17 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.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/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/disk_smart.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/nvdia.dart';
import 'package:server_box/data/model/server/sensors.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/server.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/app/shell_func.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/conn.dart';
class ServerStatusUpdateReq { class ServerStatusUpdateReq {
final ServerStatus ss; final ServerStatus ss;
final List<String> segments; final List<String> segments;
@@ -38,7 +38,8 @@ Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async { Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
final segments = req.segments; final segments = req.segments;
final time = int.tryParse(StatusCmdType.time.find(segments)) ?? final time =
int.tryParse(StatusCmdType.time.find(segments)) ??
DateTime.now().millisecondsSinceEpoch ~/ 1000; DateTime.now().millisecondsSinceEpoch ~/ 1000;
try { try {
@@ -49,9 +50,7 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
} }
try { try {
final sys = _parseSysVer( final sys = _parseSysVer(StatusCmdType.sys.find(segments));
StatusCmdType.sys.find(segments),
);
if (sys != null) { if (sys != null) {
req.ss.more[StatusCmdType.sys] = sys; req.ss.more[StatusCmdType.sys] = sys;
} }
@@ -131,6 +130,13 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
Loggers.app.warning(e, s); Loggers.app.warning(e, s);
} }
try {
final smarts = DiskSmart.parse(StatusCmdType.diskSmart.find(segments));
req.ss.diskSmart = smarts;
} catch (e, s) {
Loggers.app.warning(e, s);
}
try { try {
req.ss.nvidia = NvidiaSmi.fromXml(StatusCmdType.nvidia.find(segments)); req.ss.nvidia = NvidiaSmi.fromXml(StatusCmdType.nvidia.find(segments));
} catch (e, s) { } catch (e, s) {

View File

@@ -1,42 +1,37 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:xterm/core.dart'; import 'package:xterm/core.dart';
part 'snippet.g.dart'; part 'snippet.g.dart';
part 'snippet.freezed.dart';
@JsonSerializable() @freezed
@HiveType(typeId: 2) class Snippet with _$Snippet {
class Snippet { const factory Snippet({
@HiveField(0) required String name,
final String name; required String script,
@HiveField(1) List<String>? tags,
final String script; String? note,
@HiveField(2)
final List<String>? tags;
@HiveField(3)
final String? note;
/// List of server id that this snippet should be auto run on /// List of server id that this snippet should be auto run on
@HiveField(4) List<String>? autoRunOn,
final List<String>? autoRunOn; }) = _Snippet;
const Snippet({ factory Snippet.fromJson(Map<String, dynamic> json) => _$SnippetFromJson(json);
required this.name,
required this.script,
this.tags,
this.note,
this.autoRunOn,
});
factory Snippet.fromJson(Map<String, dynamic> json) => static const example = Snippet(
_$SnippetFromJson(json); name: 'example',
script: 'echo hello',
Map<String, dynamic> toJson() => _$SnippetToJson(this); tags: ['tag'],
note: 'note',
autoRunOn: ['server_id'],
);
}
extension SnippetX on Snippet {
static final fmtFinder = RegExp(r'\$\{[^{}]+\}'); static final fmtFinder = RegExp(r'\$\{[^{}]+\}');
String fmtWithSpi(Spi spi) { String fmtWithSpi(Spi spi) {
@@ -175,14 +170,6 @@ class Snippet {
r'${ctrl': TerminalKey.control, r'${ctrl': TerminalKey.control,
r'${alt': TerminalKey.alt, r'${alt': TerminalKey.alt,
}; };
static const example = Snippet(
name: 'example',
script: 'echo hello',
tags: ['tag'],
note: 'note',
autoRunOn: ['server_id'],
);
} }
class SnippetResult { class SnippetResult {

View File

@@ -0,0 +1,288 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'snippet.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
Snippet _$SnippetFromJson(Map<String, dynamic> json) {
return _Snippet.fromJson(json);
}
/// @nodoc
mixin _$Snippet {
String get name => throw _privateConstructorUsedError;
String get script => throw _privateConstructorUsedError;
List<String>? get tags => throw _privateConstructorUsedError;
String? get note => throw _privateConstructorUsedError;
/// List of server id that this snippet should be auto run on
List<String>? get autoRunOn => throw _privateConstructorUsedError;
/// Serializes this Snippet to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Snippet
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnippetCopyWith<Snippet> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnippetCopyWith<$Res> {
factory $SnippetCopyWith(Snippet value, $Res Function(Snippet) then) =
_$SnippetCopyWithImpl<$Res, Snippet>;
@useResult
$Res call({
String name,
String script,
List<String>? tags,
String? note,
List<String>? autoRunOn,
});
}
/// @nodoc
class _$SnippetCopyWithImpl<$Res, $Val extends Snippet>
implements $SnippetCopyWith<$Res> {
_$SnippetCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Snippet
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? script = null,
Object? tags = freezed,
Object? note = freezed,
Object? autoRunOn = freezed,
}) {
return _then(
_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
script: null == script
? _value.script
: script // ignore: cast_nullable_to_non_nullable
as String,
tags: freezed == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>?,
note: freezed == note
? _value.note
: note // ignore: cast_nullable_to_non_nullable
as String?,
autoRunOn: freezed == autoRunOn
? _value.autoRunOn
: autoRunOn // ignore: cast_nullable_to_non_nullable
as List<String>?,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$SnippetImplCopyWith<$Res> implements $SnippetCopyWith<$Res> {
factory _$$SnippetImplCopyWith(
_$SnippetImpl value,
$Res Function(_$SnippetImpl) then,
) = __$$SnippetImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
String name,
String script,
List<String>? tags,
String? note,
List<String>? autoRunOn,
});
}
/// @nodoc
class __$$SnippetImplCopyWithImpl<$Res>
extends _$SnippetCopyWithImpl<$Res, _$SnippetImpl>
implements _$$SnippetImplCopyWith<$Res> {
__$$SnippetImplCopyWithImpl(
_$SnippetImpl _value,
$Res Function(_$SnippetImpl) _then,
) : super(_value, _then);
/// Create a copy of Snippet
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? script = null,
Object? tags = freezed,
Object? note = freezed,
Object? autoRunOn = freezed,
}) {
return _then(
_$SnippetImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
script: null == script
? _value.script
: script // ignore: cast_nullable_to_non_nullable
as String,
tags: freezed == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>?,
note: freezed == note
? _value.note
: note // ignore: cast_nullable_to_non_nullable
as String?,
autoRunOn: freezed == autoRunOn
? _value._autoRunOn
: autoRunOn // ignore: cast_nullable_to_non_nullable
as List<String>?,
),
);
}
}
/// @nodoc
@JsonSerializable()
class _$SnippetImpl implements _Snippet {
const _$SnippetImpl({
required this.name,
required this.script,
final List<String>? tags,
this.note,
final List<String>? autoRunOn,
}) : _tags = tags,
_autoRunOn = autoRunOn;
factory _$SnippetImpl.fromJson(Map<String, dynamic> json) =>
_$$SnippetImplFromJson(json);
@override
final String name;
@override
final String script;
final List<String>? _tags;
@override
List<String>? get tags {
final value = _tags;
if (value == null) return null;
if (_tags is EqualUnmodifiableListView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final String? note;
/// List of server id that this snippet should be auto run on
final List<String>? _autoRunOn;
/// List of server id that this snippet should be auto run on
@override
List<String>? get autoRunOn {
final value = _autoRunOn;
if (value == null) return null;
if (_autoRunOn is EqualUnmodifiableListView) return _autoRunOn;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'Snippet(name: $name, script: $script, tags: $tags, note: $note, autoRunOn: $autoRunOn)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnippetImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.script, script) || other.script == script) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
(identical(other.note, note) || other.note == note) &&
const DeepCollectionEquality().equals(
other._autoRunOn,
_autoRunOn,
));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
name,
script,
const DeepCollectionEquality().hash(_tags),
note,
const DeepCollectionEquality().hash(_autoRunOn),
);
/// Create a copy of Snippet
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnippetImplCopyWith<_$SnippetImpl> get copyWith =>
__$$SnippetImplCopyWithImpl<_$SnippetImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnippetImplToJson(this);
}
}
abstract class _Snippet implements Snippet {
const factory _Snippet({
required final String name,
required final String script,
final List<String>? tags,
final String? note,
final List<String>? autoRunOn,
}) = _$SnippetImpl;
factory _Snippet.fromJson(Map<String, dynamic> json) = _$SnippetImpl.fromJson;
@override
String get name;
@override
String get script;
@override
List<String>? get tags;
@override
String? get note;
/// List of server id that this snippet should be auto run on
@override
List<String>? get autoRunOn;
/// Create a copy of Snippet
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnippetImplCopyWith<_$SnippetImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -2,61 +2,12 @@
part of 'snippet.dart'; part of 'snippet.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class SnippetAdapter extends TypeAdapter<Snippet> {
@override
final int typeId = 2;
@override
Snippet read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Snippet(
name: fields[0] as String,
script: fields[1] as String,
tags: (fields[2] as List?)?.cast<String>(),
note: fields[3] as String?,
autoRunOn: (fields[4] as List?)?.cast<String>(),
);
}
@override
void write(BinaryWriter writer, Snippet obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.script)
..writeByte(2)
..write(obj.tags)
..writeByte(3)
..write(obj.note)
..writeByte(4)
..write(obj.autoRunOn);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SnippetAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
Snippet _$SnippetFromJson(Map<String, dynamic> json) => Snippet( _$SnippetImpl _$$SnippetImplFromJson(Map<String, dynamic> json) =>
_$SnippetImpl(
name: json['name'] as String, name: json['name'] as String,
script: json['script'] as String, script: json['script'] as String,
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(), tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
@@ -66,7 +17,8 @@ Snippet _$SnippetFromJson(Map<String, dynamic> json) => Snippet(
.toList(), .toList(),
); );
Map<String, dynamic> _$SnippetToJson(Snippet instance) => <String, dynamic>{ Map<String, dynamic> _$$SnippetImplToJson(_$SnippetImpl instance) =>
<String, dynamic>{
'name': instance.name, 'name': instance.name,
'script': instance.script, 'script': instance.script,
'tags': instance.tags, 'tags': instance.tags,

View File

@@ -1,19 +1,14 @@
import 'dart:io'; import 'dart:io';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:wake_on_lan/wake_on_lan.dart'; import 'package:wake_on_lan/wake_on_lan.dart';
part 'wol_cfg.g.dart'; part 'wol_cfg.g.dart';
@JsonSerializable() @JsonSerializable(includeIfNull: false)
@HiveType(typeId: 8)
final class WakeOnLanCfg { final class WakeOnLanCfg {
@HiveField(0)
final String mac; final String mac;
@HiveField(1)
final String ip; final String ip;
@HiveField(2)
final String? pwd; final String? pwd;
const WakeOnLanCfg({ const WakeOnLanCfg({
@@ -26,18 +21,12 @@ final class WakeOnLanCfg {
final macValidation = MACAddress.validate(mac); final macValidation = MACAddress.validate(mac);
final ipValidation = IPAddress.validate( final ipValidation = IPAddress.validate(
ip, ip,
type: ip.contains(':') type: ip.contains(':') ? InternetAddressType.IPv6 : InternetAddressType.IPv4,
? InternetAddressType.IPv6
: InternetAddressType.IPv4,
); );
final pwdValidation = pwd != null final pwdValidation = pwd != null ? SecureONPassword.validate(pwd) : (state: true, error: null);
? SecureONPassword.validate(pwd)
: (state: true, error: null);
final valid = final valid = macValidation.state && ipValidation.state && pwdValidation.state;
macValidation.state && ipValidation.state && pwdValidation.state; final err = macValidation.error ?? ipValidation.error ?? pwdValidation.error;
final err =
macValidation.error ?? ipValidation.error ?? pwdValidation.error;
return (err, valid); return (err, valid);
} }
@@ -56,8 +45,7 @@ final class WakeOnLanCfg {
); );
} }
factory WakeOnLanCfg.fromJson(Map<String, dynamic> json) => factory WakeOnLanCfg.fromJson(Map<String, dynamic> json) => _$WakeOnLanCfgFromJson(json);
_$WakeOnLanCfgFromJson(json);
Map<String, dynamic> toJson() => _$WakeOnLanCfgToJson(this); Map<String, dynamic> toJson() => _$WakeOnLanCfgToJson(this);
} }

View File

@@ -2,63 +2,19 @@
part of 'wol_cfg.dart'; part of 'wol_cfg.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class WakeOnLanCfgAdapter extends TypeAdapter<WakeOnLanCfg> {
@override
final int typeId = 8;
@override
WakeOnLanCfg read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return WakeOnLanCfg(
mac: fields[0] as String,
ip: fields[1] as String,
pwd: fields[2] as String?,
);
}
@override
void write(BinaryWriter writer, WakeOnLanCfg obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.mac)
..writeByte(1)
..write(obj.ip)
..writeByte(2)
..write(obj.pwd);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is WakeOnLanCfgAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
WakeOnLanCfg _$WakeOnLanCfgFromJson(Map<String, dynamic> json) => WakeOnLanCfg( WakeOnLanCfg _$WakeOnLanCfgFromJson(Map<String, dynamic> json) => WakeOnLanCfg(
mac: json['mac'] as String, mac: json['mac'] as String,
ip: json['ip'] as String, ip: json['ip'] as String,
pwd: json['pwd'] as String?, pwd: json['pwd'] as String?,
); );
Map<String, dynamic> _$WakeOnLanCfgToJson(WakeOnLanCfg instance) => Map<String, dynamic> _$WakeOnLanCfgToJson(WakeOnLanCfg instance) =>
<String, dynamic>{ <String, dynamic>{
'mac': instance.mac, 'mac': instance.mac,
'ip': instance.ip, 'ip': instance.ip,
'pwd': instance.pwd, if (instance.pwd case final value?) 'pwd': value,
}; };

View File

@@ -36,7 +36,7 @@ class _AbsolutePath {
_path = newPath; _path = newPath;
return; return;
} }
_path = _path.joinPath(newPath, seperator: _sep); _path = _path.joinPath(newPath, separator: _sep);
} }
bool undo() { bool undo() {

View File

@@ -21,7 +21,7 @@ class SftpReq {
} }
if (spi.jumpId != null) { if (spi.jumpId != null) {
jumpSpi = Stores.server.box.get(spi.jumpId); jumpSpi = Stores.server.box.get(spi.jumpId);
jumpPrivateKey = Stores.key.get(jumpSpi?.keyId)?.key; jumpPrivateKey = Stores.key.fetchOne(jumpSpi?.keyId)?.key;
} }
} }
} }

View File

@@ -60,8 +60,6 @@ Future<void> isolateMessageHandler(
case SftpReqType.upload: case SftpReqType.upload:
await _upload(data, mainSendPort, sendError); await _upload(data, mainSendPort, sendError);
break; break;
default:
sendError(Exception('unknown type'));
} }
break; break;
default: default:

View File

@@ -1,81 +1,56 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:xterm/core.dart'; import 'package:xterm/core.dart';
part 'virtual_key.g.dart';
enum VirtualKeyFunc { toggleIME, backspace, clipboard, snippet, file } enum VirtualKeyFunc { toggleIME, backspace, clipboard, snippet, file }
@HiveType(typeId: 4)
enum VirtKey { enum VirtKey {
@HiveField(0)
esc, esc,
@HiveField(1)
alt, alt,
@HiveField(2)
home, home,
@HiveField(3)
up, up,
@HiveField(4)
end, end,
@HiveField(5)
sftp, sftp,
@HiveField(6)
snippet, snippet,
@HiveField(7)
tab, tab,
@HiveField(8)
ctrl, ctrl,
@HiveField(9)
left, left,
@HiveField(10)
down, down,
@HiveField(11)
right, right,
@HiveField(12)
clipboard, clipboard,
@HiveField(13)
ime, ime,
@HiveField(14)
pgup, pgup,
@HiveField(15)
pgdn, pgdn,
@HiveField(16)
slash, slash,
@HiveField(17)
backSlash, backSlash,
@HiveField(18)
underscore, underscore,
@HiveField(19)
plus, plus,
@HiveField(20)
equal, equal,
@HiveField(21)
minus, minus,
@HiveField(22)
parenLeft, parenLeft,
@HiveField(23)
parenRight, parenRight,
@HiveField(24)
bracketLeft, bracketLeft,
@HiveField(25)
bracketRight, bracketRight,
@HiveField(26)
braceLeft, braceLeft,
@HiveField(27)
braceRight, braceRight,
@HiveField(28)
chevronLeft, chevronLeft,
@HiveField(29)
chevronRight, chevronRight,
@HiveField(30)
colon, colon,
@HiveField(31)
semicolon, semicolon,
; f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12;
} }
extension VirtKeyX on VirtKey { extension VirtKeyX on VirtKey {
@@ -146,6 +121,18 @@ extension VirtKeyX on VirtKey {
VirtKey.right => TerminalKey.arrowRight, VirtKey.right => TerminalKey.arrowRight,
VirtKey.pgup => TerminalKey.pageUp, VirtKey.pgup => TerminalKey.pageUp,
VirtKey.pgdn => TerminalKey.pageDown, VirtKey.pgdn => TerminalKey.pageDown,
VirtKey.f1 => TerminalKey.f1,
VirtKey.f2 => TerminalKey.f2,
VirtKey.f3 => TerminalKey.f3,
VirtKey.f4 => TerminalKey.f4,
VirtKey.f5 => TerminalKey.f5,
VirtKey.f6 => TerminalKey.f6,
VirtKey.f7 => TerminalKey.f7,
VirtKey.f8 => TerminalKey.f8,
VirtKey.f9 => TerminalKey.f9,
VirtKey.f10 => TerminalKey.f10,
VirtKey.f11 => TerminalKey.f11,
VirtKey.f12 => TerminalKey.f12,
_ => null, _ => null,
}; };

View File

@@ -1,196 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'virtual_key.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class VirtKeyAdapter extends TypeAdapter<VirtKey> {
@override
final int typeId = 4;
@override
VirtKey read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return VirtKey.esc;
case 1:
return VirtKey.alt;
case 2:
return VirtKey.home;
case 3:
return VirtKey.up;
case 4:
return VirtKey.end;
case 5:
return VirtKey.sftp;
case 6:
return VirtKey.snippet;
case 7:
return VirtKey.tab;
case 8:
return VirtKey.ctrl;
case 9:
return VirtKey.left;
case 10:
return VirtKey.down;
case 11:
return VirtKey.right;
case 12:
return VirtKey.clipboard;
case 13:
return VirtKey.ime;
case 14:
return VirtKey.pgup;
case 15:
return VirtKey.pgdn;
case 16:
return VirtKey.slash;
case 17:
return VirtKey.backSlash;
case 18:
return VirtKey.underscore;
case 19:
return VirtKey.plus;
case 20:
return VirtKey.equal;
case 21:
return VirtKey.minus;
case 22:
return VirtKey.parenLeft;
case 23:
return VirtKey.parenRight;
case 24:
return VirtKey.bracketLeft;
case 25:
return VirtKey.bracketRight;
case 26:
return VirtKey.braceLeft;
case 27:
return VirtKey.braceRight;
case 28:
return VirtKey.chevronLeft;
case 29:
return VirtKey.chevronRight;
case 30:
return VirtKey.colon;
case 31:
return VirtKey.semicolon;
default:
return VirtKey.esc;
}
}
@override
void write(BinaryWriter writer, VirtKey obj) {
switch (obj) {
case VirtKey.esc:
writer.writeByte(0);
break;
case VirtKey.alt:
writer.writeByte(1);
break;
case VirtKey.home:
writer.writeByte(2);
break;
case VirtKey.up:
writer.writeByte(3);
break;
case VirtKey.end:
writer.writeByte(4);
break;
case VirtKey.sftp:
writer.writeByte(5);
break;
case VirtKey.snippet:
writer.writeByte(6);
break;
case VirtKey.tab:
writer.writeByte(7);
break;
case VirtKey.ctrl:
writer.writeByte(8);
break;
case VirtKey.left:
writer.writeByte(9);
break;
case VirtKey.down:
writer.writeByte(10);
break;
case VirtKey.right:
writer.writeByte(11);
break;
case VirtKey.clipboard:
writer.writeByte(12);
break;
case VirtKey.ime:
writer.writeByte(13);
break;
case VirtKey.pgup:
writer.writeByte(14);
break;
case VirtKey.pgdn:
writer.writeByte(15);
break;
case VirtKey.slash:
writer.writeByte(16);
break;
case VirtKey.backSlash:
writer.writeByte(17);
break;
case VirtKey.underscore:
writer.writeByte(18);
break;
case VirtKey.plus:
writer.writeByte(19);
break;
case VirtKey.equal:
writer.writeByte(20);
break;
case VirtKey.minus:
writer.writeByte(21);
break;
case VirtKey.parenLeft:
writer.writeByte(22);
break;
case VirtKey.parenRight:
writer.writeByte(23);
break;
case VirtKey.bracketLeft:
writer.writeByte(24);
break;
case VirtKey.bracketRight:
writer.writeByte(25);
break;
case VirtKey.braceLeft:
writer.writeByte(26);
break;
case VirtKey.braceRight:
writer.writeByte(27);
break;
case VirtKey.chevronLeft:
writer.writeByte(28);
break;
case VirtKey.chevronRight:
writer.writeByte(29);
break;
case VirtKey.colon:
writer.writeByte(30);
break;
case VirtKey.semicolon:
writer.writeByte(31);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is VirtKeyAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -1,7 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
final class AppProvider { part 'app.g.dart';
const AppProvider._(); part 'app.freezed.dart';
static BuildContext? ctx; @freezed
class AppState with _$AppState {
const factory AppState({
@Default(false) bool desktopMode,
}) = _AppState;
}
@Riverpod(keepAlive: true)
class AppProvider extends _$AppProvider {
static BuildContext? ctx;
@override
AppState build() {
return const AppState();
}
void setDesktop(bool desktopMode) {
state = state.copyWith(desktopMode: desktopMode);
}
} }

View File

@@ -0,0 +1,148 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'app.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$AppState {
bool get desktopMode => throw _privateConstructorUsedError;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AppStateCopyWith<AppState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AppStateCopyWith<$Res> {
factory $AppStateCopyWith(AppState value, $Res Function(AppState) then) =
_$AppStateCopyWithImpl<$Res, AppState>;
@useResult
$Res call({bool desktopMode});
}
/// @nodoc
class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
implements $AppStateCopyWith<$Res> {
_$AppStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? desktopMode = null}) {
return _then(
_value.copyWith(
desktopMode: null == desktopMode
? _value.desktopMode
: desktopMode // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$AppStateImplCopyWith<$Res>
implements $AppStateCopyWith<$Res> {
factory _$$AppStateImplCopyWith(
_$AppStateImpl value,
$Res Function(_$AppStateImpl) then,
) = __$$AppStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool desktopMode});
}
/// @nodoc
class __$$AppStateImplCopyWithImpl<$Res>
extends _$AppStateCopyWithImpl<$Res, _$AppStateImpl>
implements _$$AppStateImplCopyWith<$Res> {
__$$AppStateImplCopyWithImpl(
_$AppStateImpl _value,
$Res Function(_$AppStateImpl) _then,
) : super(_value, _then);
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? desktopMode = null}) {
return _then(
_$AppStateImpl(
desktopMode: null == desktopMode
? _value.desktopMode
: desktopMode // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$AppStateImpl implements _AppState {
const _$AppStateImpl({this.desktopMode = false});
@override
@JsonKey()
final bool desktopMode;
@override
String toString() {
return 'AppState(desktopMode: $desktopMode)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AppStateImpl &&
(identical(other.desktopMode, desktopMode) ||
other.desktopMode == desktopMode));
}
@override
int get hashCode => Object.hash(runtimeType, desktopMode);
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AppStateImplCopyWith<_$AppStateImpl> get copyWith =>
__$$AppStateImplCopyWithImpl<_$AppStateImpl>(this, _$identity);
}
abstract class _AppState implements AppState {
const factory _AppState({final bool desktopMode}) = _$AppStateImpl;
@override
bool get desktopMode;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AppStateImplCopyWith<_$AppStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$appProviderHash() => r'8378ec9d0a9c8d99cc05805047cd2d52ac4dbb56';
/// See also [AppProvider].
@ProviderFor(AppProvider)
final appProviderProvider = NotifierProvider<AppProvider, AppState>.internal(
AppProvider.new,
name: r'appProviderProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appProviderHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppProvider = Notifier<AppState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -5,10 +5,10 @@ import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/extension/ssh_client.dart'; import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/container/image.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/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/model/container/type.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:computer/computer.dart'; import 'package:computer/computer.dart';
import 'package:dartssh2/dartssh2.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
@@ -10,7 +11,6 @@ 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/error.dart';
import 'package:server_box/data/model/server/pve.dart'; import 'package:server_box/data/model/server/pve.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:dartssh2/dartssh2.dart';
typedef PveCtrlFunc = Future<bool> Function(String node, String id); typedef PveCtrlFunc = Future<bool> Function(String node, String id);
@@ -47,11 +47,11 @@ final class PveProvider extends ChangeNotifier {
final client = HttpClient(); final client = HttpClient();
client.connectionFactory = cf; client.connectionFactory = cf;
if (_ignoreCert) { if (_ignoreCert) {
client.badCertificateCallback = (_, __, ___) => true; client.badCertificateCallback = (_, _, _) => true;
} }
return client; return client;
}, },
validateCertificate: _ignoreCert ? (_, __, ___) => true : null, validateCertificate: _ignoreCert ? (_, _, _) => true : null,
); );
final data = ValueNotifier<PveRes?>(null); final data = ValueNotifier<PveRes?>(null);

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
// import 'dart:io'; // import 'dart:io';
import 'package:computer/computer.dart'; import 'package:computer/computer.dart';
@@ -6,18 +7,17 @@ import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/core/extension/ssh_client.dart'; import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/core/sync.dart'; import 'package:server_box/core/sync.dart';
import 'package:server_box/core/utils/server.dart';
import 'package:server_box/core/utils/ssh_auth.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/error.dart';
import 'package:server_box/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/core/utils/server.dart';
import 'package:server_box/data/model/server/server.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/server_status_update_req.dart'; import 'package:server_box/data/model/server/server_status_update_req.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/server/try_limiter.dart'; import 'package:server_box/data/model/server/try_limiter.dart';
import 'package:server_box/data/res/status.dart'; import 'package:server_box/data/res/status.dart';
import 'package:server_box/data/res/store.dart';
class ServerProvider extends Provider { class ServerProvider extends Provider {
const ServerProvider._(); const ServerProvider._();
@@ -45,22 +45,20 @@ class ServerProvider extends Provider {
for (int idx = 0; idx < spis.length; idx++) { for (int idx = 0; idx < spis.length; idx++) {
final spi = spis[idx]; final spi = spis[idx];
final originServer = oldServers[spi.id]; final originServer = oldServers[spi.id];
final newServer = genServer(spi);
/// #258 /// #258
/// If not [shouldReconnect], then keep the old state. /// If not [shouldReconnect], then keep the old state.
if (originServer != null && if (originServer != null && !originServer.value.spi.shouldReconnect(spi)) {
!originServer.value.spi.shouldReconnect(spi)) { originServer.value.spi = spi;
newServer.conn = originServer.value.conn; servers[spi.id] = originServer;
} else {
final newServer = genServer(spi);
servers[spi.id] = newServer.vn;
} }
servers[spi.id] = newServer.vn;
} }
final serverOrder_ = Stores.setting.serverOrder.fetch(); final serverOrder_ = Stores.setting.serverOrder.fetch();
if (serverOrder_.isNotEmpty) { if (serverOrder_.isNotEmpty) {
spis.reorder( spis.reorder(order: serverOrder_, finder: (n, id) => n.id == id);
order: serverOrder_,
finder: (n, id) => n.id == id,
);
serverOrder.value.addAll(spis.map((e) => e.id)); serverOrder.value.addAll(spis.map((e) => e.id));
} else { } else {
serverOrder.value.addAll(servers.keys); serverOrder.value.addAll(servers.keys);
@@ -105,31 +103,30 @@ class ServerProvider extends Provider {
/// if [spi] is specificed then only refresh this server /// if [spi] is specificed then only refresh this server
/// [onlyFailed] only refresh failed servers /// [onlyFailed] only refresh failed servers
static Future<void> refresh({ static Future<void> refresh({Spi? spi, bool onlyFailed = false}) async {
Spi? spi,
bool onlyFailed = false,
}) async {
if (spi != null) { if (spi != null) {
_manualDisconnectedIds.remove(spi.id); _manualDisconnectedIds.remove(spi.id);
await _getData(spi); await _getData(spi);
return; return;
} }
await Future.wait(servers.values.map((val) async { await Future.wait(
final s = val.value; servers.values.map((val) async {
if (onlyFailed) { final s = val.value;
if (s.conn != ServerConn.failed) return; if (onlyFailed) {
TryLimiter.reset(s.spi.id); if (s.conn != ServerConn.failed) return;
} TryLimiter.reset(s.spi.id);
}
if (_manualDisconnectedIds.contains(s.spi.id)) return; if (_manualDisconnectedIds.contains(s.spi.id)) return;
if (s.conn == ServerConn.disconnected && !s.spi.autoConnect) { if (s.conn == ServerConn.disconnected && !s.spi.autoConnect) {
return; return;
} }
return await _getData(s.spi); return await _getData(s.spi);
})); }),
);
} }
static Future<void> startAutoRefresh() async { static Future<void> startAutoRefresh() async {
@@ -174,12 +171,16 @@ class ServerProvider extends Provider {
static void _closeOneServer(String id) { static void _closeOneServer(String id) {
final s = servers[id]; final s = servers[id];
final item = s?.value; if (s == null) {
item?.client?.close(); Loggers.app.warning('Server with id $id not found');
item?.client = null; return;
item?.conn = ServerConn.disconnected; }
final item = s.value;
item.client?.close();
item.client = null;
item.conn = ServerConn.disconnected;
_manualDisconnectedIds.add(id); _manualDisconnectedIds.add(id);
s?.notify(); s.notify();
} }
static void addServer(Spi spi) { static void addServer(Spi spi) {
@@ -208,14 +209,12 @@ class ServerProvider extends Provider {
serverOrder.value.clear(); serverOrder.value.clear();
serverOrder.notify(); serverOrder.notify();
Stores.setting.serverOrder.put(serverOrder.value); Stores.setting.serverOrder.put(serverOrder.value);
Stores.server.deleteAll(); Stores.server.clear();
_updateTags(); _updateTags();
bakSync.sync(milliDelay: 1000);
} }
static Future<void> updateServer( static Future<void> updateServer(Spi old, Spi newSpi) async {
Spi old,
Spi newSpi,
) async {
if (old != newSpi) { if (old != newSpi) {
Stores.server.update(old, newSpi); Stores.server.update(old, newSpi);
servers[old.id]?.value.spi = newSpi; servers[old.id]?.value.spi = newSpi;
@@ -236,7 +235,7 @@ class ServerProvider extends Provider {
} }
} }
_updateTags(); _updateTags();
bakSync.sync(); bakSync.sync(milliDelay: 1000);
} }
static void _setServerState(VNode<Server> s, ServerConn ss) { static void _setServerState(VNode<Server> s, ServerConn ss) {
@@ -306,14 +305,11 @@ class ServerProvider extends Provider {
_setServerState(s, ServerConn.connected); _setServerState(s, ServerConn.connected);
try { try {
final (_, writeScriptResult) = await sv.client!.exec( final (_, writeScriptResult) = await sv.client!.exec((session) async {
(session) async { final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List; session.stdin.add(scriptRaw);
session.stdin.add(scriptRaw); session.stdin.close();
session.stdin.close(); }, entry: ShellFunc.getInstallShellCmd(spi.id));
},
entry: ShellFunc.getInstallShellCmd(spi.id),
);
if (writeScriptResult.isNotEmpty) { if (writeScriptResult.isNotEmpty) {
ShellFunc.switchScriptDir(spi.id); ShellFunc.switchScriptDir(spi.id);
throw writeScriptResult; throw writeScriptResult;
@@ -365,10 +361,7 @@ class ServerProvider extends Provider {
} }
} }
TryLimiter.inc(sid); TryLimiter.inc(sid);
sv.status.err = SSHErr( sv.status.err = SSHErr(type: SSHErrType.segements, message: 'Seperate segments failed, raw:\n$raw');
type: SSHErrType.segements,
message: 'Seperate segments failed, raw:\n$raw',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
return; return;
} }
@@ -407,17 +400,10 @@ class ServerProvider extends Provider {
system: systemType, system: systemType,
customCmds: spi.custom?.cmds ?? {}, customCmds: spi.custom?.cmds ?? {},
); );
sv.status = await Computer.shared.start( sv.status = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${sv.id}>');
getStatus,
req,
taskName: 'StatusUpdateReq<${sv.id}>',
);
} catch (e, trace) { } catch (e, trace) {
TryLimiter.inc(sid); TryLimiter.inc(sid);
sv.status.err = SSHErr( sv.status.err = SSHErr(type: SSHErrType.getStatus, message: 'Parse failed: $e\n\n$raw');
type: SSHErrType.getStatus,
message: 'Parse failed: $e\n\n$raw',
);
_setServerState(s, ServerConn.failed); _setServerState(s, ServerConn.failed);
Loggers.app.warning('Server status', e, trace); Loggers.app.warning('Server status', e, trace);
return; return;

View File

@@ -14,11 +14,7 @@ class SftpProvider extends Provider {
} }
static int add(SftpReq req, {Completer? completer}) { static int add(SftpReq req, {Completer? completer}) {
final reqStat = SftpReqStatus( final reqStat = SftpReqStatus(notifyListeners: status.notify, completer: completer, req: req);
notifyListeners: status.notify,
completer: completer,
req: req,
);
status.value.add(reqStat); status.value.add(reqStat);
status.notify(); status.notify();
return reqStat.id; return reqStat.id;
@@ -34,6 +30,10 @@ class SftpProvider extends Provider {
static void cancel(int id) { static void cancel(int id) {
final idx = status.value.indexWhere((e) => e.id == id); final idx = status.value.indexWhere((e) => e.id == id);
if (idx < 0 || idx >= status.value.length) {
dprint('SftpProvider.cancel: id $id not found');
return;
}
status.value[idx].dispose(); status.value[idx].dispose();
status.value.removeAt(idx); status.value.removeAt(idx);
status.notify(); status.notify();

View File

@@ -3,6 +3,6 @@
abstract class BuildData { abstract class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 1104; static const int build = 1185;
static const int script = 58; static const int script = 63;
} }

View File

@@ -17,6 +17,11 @@ abstract final class GithubIds {
'dccif', 'dccif',
'mikropsoft', 'mikropsoft',
'CakesTwix', 'CakesTwix',
'dsvf',
'fei1025',
'MasedMSD',
'GitGitro',
'Shin-suechtig',
}; };
static const participants = <GhId>{ static const participants = <GhId>{
@@ -99,6 +104,20 @@ abstract final class GithubIds {
'88484396', '88484396',
'honggeigei', 'honggeigei',
'likecreep', 'likecreep',
'axlrose',
'immortal521',
'PRO-2684',
'Xiaobao-Yang',
'Mrhs121',
'Fudiautobi',
'papaj-na-wrotkach',
'kid1412621',
'smanx',
'xuanyue1024',
'RuofengX',
'rhwong',
'AstroEngineeer',
'mochasweet',
}; };
} }

View File

@@ -1,6 +0,0 @@
import 'package:fl_lib/fl_lib.dart';
abstract final class RNodes {
static final app = RNode();
static final dark = false.vn;
}

View File

@@ -1,64 +1,40 @@
import 'package:server_box/data/model/server/server.dart'; import 'package:server_box/data/model/server/conn.dart';
import 'package:server_box/data/model/server/temp.dart';
import 'package:server_box/data/model/server/cpu.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/disk.dart';
import 'package:server_box/data/model/server/memory.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/net_speed.dart';
import 'package:server_box/data/model/server/conn.dart'; import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/server/temp.dart';
abstract final class InitStatus { abstract final class InitStatus {
static SingleCpuCore get _initOneTimeCpuStatus => SingleCpuCore( static SingleCpuCore get _initOneTimeCpuStatus =>
'cpu', SingleCpuCore('cpu', 0, 0, 0, 0, 0, 0, 0);
0, static Cpus get cpus =>
0, Cpus([_initOneTimeCpuStatus], [_initOneTimeCpuStatus]);
0, static NetSpeedPart get _initNetSpeedPart =>
0, NetSpeedPart('', BigInt.zero, BigInt.zero, 0);
0, static NetSpeed get netSpeed =>
0, NetSpeed([_initNetSpeedPart], [_initNetSpeedPart]);
0,
);
static Cpus get cpus => Cpus(
[_initOneTimeCpuStatus],
[_initOneTimeCpuStatus],
);
static NetSpeedPart get _initNetSpeedPart => NetSpeedPart(
'',
BigInt.zero,
BigInt.zero,
0,
);
static NetSpeed get netSpeed => NetSpeed(
[_initNetSpeedPart],
[_initNetSpeedPart],
);
static ServerStatus get status => ServerStatus( static ServerStatus get status => ServerStatus(
cpu: cpus, cpu: cpus,
mem: const Memory( mem: const Memory(total: 1, free: 1, avail: 1),
total: 1, disk: [
free: 1, Disk(
avail: 1, path: '/',
), mount: '/',
disk: [ usedPercent: 0,
Disk( used: BigInt.zero,
fs: '/', size: BigInt.one,
mount: '/', avail: BigInt.zero,
usedPercent: 0, ),
used: BigInt.zero, ],
size: BigInt.one, tcp: const Conn(maxConn: 0, active: 0, passive: 0, fail: 0),
avail: BigInt.zero, netSpeed: netSpeed,
) swap: const Swap(total: 0, free: 0, cached: 0),
], system: SystemType.linux,
tcp: const Conn(maxConn: 0, active: 0, passive: 0, fail: 0), temps: Temperatures(),
netSpeed: netSpeed, diskIO: DiskIO([], []),
swap: const Swap( diskSmart: const [],
total: 0, );
free: 0,
cached: 0,
),
system: SystemType.linux,
temps: Temperatures(),
diskIO: DiskIO([], []),
);
} }

View File

@@ -1,7 +1,6 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/store/container.dart'; import 'package:server_box/data/store/container.dart';
import 'package:server_box/data/store/history.dart'; import 'package:server_box/data/store/history.dart';
import 'package:server_box/data/store/no_backup.dart';
import 'package:server_box/data/store/private_key.dart'; import 'package:server_box/data/store/private_key.dart';
import 'package:server_box/data/store/server.dart'; import 'package:server_box/data/store/server.dart';
import 'package:server_box/data/store/setting.dart'; import 'package:server_box/data/store/setting.dart';
@@ -16,7 +15,7 @@ abstract final class Stores {
static final history = HistoryStore.instance; static final history = HistoryStore.instance;
/// All stores that need backup /// All stores that need backup
static final List<PersistentStore> _allBackup = [ static final List<HiveStore> _allBackup = [
SettingStore.instance, SettingStore.instance,
ServerStore.instance, ServerStore.instance,
ContainerStore.instance, ContainerStore.instance,
@@ -27,15 +26,24 @@ abstract final class Stores {
static Future<void> init() async { static Future<void> init() async {
await Future.wait(_allBackup.map((store) => store.init())); await Future.wait(_allBackup.map((store) => store.init()));
await NoBackupStore.instance.init();
} }
static int? get lastModTime { static int get lastModTime {
int? lastModTime = 0; var lastModTime = 0;
for (final store in _allBackup) { for (final store in _allBackup) {
final last = store.box.lastModified ?? 0; final last = store.lastUpdateTs;
if (last > (lastModTime ?? 0)) { if (last == null) {
lastModTime = last; continue;
}
var lastModTimeTs = 0;
for (final item in last.entries) {
final ts = item.value;
if (ts > lastModTimeTs) {
lastModTimeTs = ts;
}
}
if (lastModTimeTs > lastModTime) {
lastModTime = lastModTimeTs;
} }
} }
return lastModTime; return lastModTime;

View File

@@ -4,7 +4,7 @@ import 'package:server_box/data/res/store.dart';
const _keyConfig = 'providerConfig'; const _keyConfig = 'providerConfig';
class ContainerStore extends PersistentStore { class ContainerStore extends HiveStore {
ContainerStore._() : super('docker'); ContainerStore._() : super('docker');
static final instance = ContainerStore._(); static final instance = ContainerStore._();
@@ -14,8 +14,7 @@ class ContainerStore extends PersistentStore {
} }
void put(String id, String host) { void put(String id, String host) {
box.put(id, host); set(id, host);
box.updateLastModified();
} }
ContainerType getType([String id = '']) { ContainerType getType([String id = '']) {
@@ -30,16 +29,17 @@ class ContainerStore extends PersistentStore {
} }
ContainerType get defaultType { ContainerType get defaultType {
if (Stores.setting.usePodman.fetch()) return ContainerType.podman; if (Stores.setting.usePodman.get()) return ContainerType.podman;
return ContainerType.docker; return ContainerType.docker;
} }
void setType(ContainerType type, [String id = '']) { void setType(ContainerType type, [String id = '']) {
if (type == defaultType) { if (type == defaultType) {
box.delete(_keyConfig + id); // box.delete(_keyConfig + id);
remove(_keyConfig + id);
} else { } else {
box.put(_keyConfig + id, type.toString()); // box.put(_keyConfig + id, type.toString());
set(_keyConfig + id, type.toString());
} }
box.updateLastModified();
} }
} }

View File

@@ -1,5 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_ce_flutter/hive_flutter.dart';
/// index from 0 -> n : latest -> oldest /// index from 0 -> n : latest -> oldest
class _ListHistory { class _ListHistory {
@@ -18,7 +18,6 @@ class _ListHistory {
_history.remove(path); _history.remove(path);
_history.insert(0, path); _history.insert(0, path);
_box.put(_name, _history); _box.put(_name, _history);
_box.updateLastModified();
} }
List get all => _history; List get all => _history;
@@ -39,13 +38,12 @@ class _MapHistory {
void put(String id, String val) { void put(String id, String val) {
_history[id] = val; _history[id] = val;
_box.put(_name, _history); _box.put(_name, _history);
_box.updateLastModified();
} }
String? fetch(String id) => _history[id]; String? fetch(String id) => _history[id];
} }
class HistoryStore extends PersistentStore { class HistoryStore extends HiveStore {
HistoryStore._() : super('history'); HistoryStore._() : super('history');
static final instance = HistoryStore._(); static final instance = HistoryStore._();
@@ -58,5 +56,6 @@ class HistoryStore extends PersistentStore {
late final sshCmds = _ListHistory(box: box, name: 'sshCmds'); late final sshCmds = _ListHistory(box: box, name: 'sshCmds');
/// Notify users that this app will write script to server to works properly /// Notify users that this app will write script to server to works properly
late final writeScriptTipShown = property('writeScriptTipShown', false); late final writeScriptTipShown =
propertyDefault('writeScriptTipShown', false);
} }

View File

@@ -1,49 +0,0 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart';
final class NoBackupStore extends PersistentStore {
NoBackupStore._() : super('no_backup');
static final instance = NoBackupStore._();
/// Only valid on iOS and macOS
late final icloudSync = property('icloudSync', false);
/// Webdav sync
late final webdavSync = property('webdavSync', false);
late final webdavUrl = property('webdavUrl', '');
late final webdavUser = property('webdavUser', '');
late final webdavPwd = property('webdavPwd', '');
void migrate() {
if (BuildData.build > 1076) return;
final settings = Stores.setting;
final icloudSync_ = settings.box.get('icloudSync');
if (icloudSync_ is bool) {
icloudSync.put(icloudSync_);
settings.box.delete('icloudSync');
}
final webdavSync_ = settings.box.get('webdavSync');
if (webdavSync_ is bool) {
webdavSync.put(webdavSync_);
settings.box.delete('webdavSync');
}
final webdavUrl_ = settings.box.get('webdavUrl');
if (webdavUrl_ is String) {
webdavUrl.put(webdavUrl_);
settings.box.delete('webdavUrl');
}
final webdavUser_ = settings.box.get('webdavUser');
if (webdavUser_ is String) {
webdavUser.put(webdavUser_);
settings.box.delete('webdavUser');
}
final webdavPwd_ = settings.box.get('webdavPwd');
if (webdavPwd_ is String) {
webdavPwd.put(webdavPwd_);
settings.box.delete('webdavPwd');
}
}
}

View File

@@ -2,35 +2,49 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
class PrivateKeyStore extends PersistentStore { class PrivateKeyStore extends HiveStore {
PrivateKeyStore._() : super('key'); PrivateKeyStore._() : super('key');
static final instance = PrivateKeyStore._(); static final instance = PrivateKeyStore._();
void put(PrivateKeyInfo info) { void put(PrivateKeyInfo info) {
box.put(info.id, info); set(info.id, info);
box.updateLastModified();
} }
List<PrivateKeyInfo> fetch() { List<PrivateKeyInfo> fetch() {
final keys = box.keys;
final ps = <PrivateKeyInfo>[]; final ps = <PrivateKeyInfo>[];
for (final key in keys) { for (final key in keys()) {
final s = box.get(key); final s = get<PrivateKeyInfo>(
if (s != null && s is PrivateKeyInfo) { key,
fromObj: (val) {
if (val is PrivateKeyInfo) return val;
if (val is Map<dynamic, dynamic>) {
final map = val.toStrDynMap;
if (map == null) return null;
try {
final pki = PrivateKeyInfo.fromJson(map as Map<String, dynamic>);
put(pki);
return pki;
} catch (e) {
dprint('Parsing PrivateKeyInfo from JSON', e);
}
}
return null;
},
);
if (s != null) {
ps.add(s); ps.add(s);
} }
} }
return ps; return ps;
} }
PrivateKeyInfo? get(String? id) { PrivateKeyInfo? fetchOne(String? id) {
if (id == null) return null; if (id == null) return null;
return box.get(id); return box.get(id);
} }
void delete(PrivateKeyInfo s) { void delete(PrivateKeyInfo s) {
box.delete(s.id); remove(s.id);
box.updateLastModified();
} }
} }

View File

@@ -1,23 +1,41 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/store/container.dart';
import 'package:server_box/data/store/setting.dart';
import 'package:server_box/data/store/snippet.dart';
class ServerStore extends PersistentStore { class ServerStore extends HiveStore {
ServerStore._() : super('server'); ServerStore._() : super('server');
static final instance = ServerStore._(); static final instance = ServerStore._();
void put(Spi info) { void put(Spi info) {
box.put(info.id, info); set(info.id, info);
box.updateLastModified();
} }
List<Spi> fetch() { List<Spi> fetch() {
final ids = box.keys;
final List<Spi> ss = []; final List<Spi> ss = [];
for (final id in ids) { for (final id in keys()) {
final s = box.get(id); final s = get<Spi>(
if (s != null && s is Spi) { id,
fromObj: (val) {
if (val is Spi) return val;
if (val is Map<dynamic, dynamic>) {
final map = val.toStrDynMap;
if (map == null) return null;
try {
final spi = Spi.fromJson(map as Map<String, dynamic>);
put(spi);
return spi;
} catch (e) {
dprint('Parsing Spi from JSON', e);
}
}
return null;
},
);
if (s != null) {
ss.add(s); ss.add(s);
} }
} }
@@ -25,13 +43,7 @@ class ServerStore extends PersistentStore {
} }
void delete(String id) { void delete(String id) {
box.delete(id); remove(id);
box.updateLastModified();
}
void deleteAll() {
box.clear();
box.updateLastModified();
} }
void update(Spi old, Spi newInfo) { void update(Spi old, Spi newInfo) {
@@ -42,5 +54,74 @@ class ServerStore extends PersistentStore {
put(newInfo); put(newInfo);
} }
bool have(Spi s) => box.get(s.id) != null; bool have(Spi s) => get(s.id) != null;
void migrateIds() {
final ss = fetch();
final idMap = <String, String>{};
// Collect all old to new ID mappings
for (final s in ss) {
final newId = s.migrateId();
if (newId == null) continue;
// Use s.oldId as the key, because s.id would be empty for a server being migrated.
// s.oldId represents the identifier used before migration.
idMap[s.oldId] = newId;
}
final srvOrder = SettingStore.instance.serverOrder.fetch();
final snippets = SnippetStore.instance.fetch();
final container = ContainerStore.instance;
bool srvOrderChanged = false;
// Update all references to the servers
for (final e in idMap.entries) {
final oldId = e.key;
final newId = e.value;
// Replace ids in ordering settings.
final srvIdx = srvOrder.indexOf(oldId);
if (srvIdx != -1) {
srvOrder[srvIdx] = newId;
srvOrderChanged = true;
}
// Replace ids in jump server settings.
final spi = get<Spi>(newId);
if (spi != null) {
final jumpId = spi.jumpId; // This could be an oldId.
// Check if this jumpId corresponds to a server that was also migrated.
if (jumpId != null && idMap.containsKey(jumpId)) {
final newJumpId = idMap[jumpId];
if (spi.jumpId != newJumpId) {
final newSpi = spi.copyWith(jumpId: newJumpId);
update(spi, newSpi);
}
}
}
// Replace ids in [Snippet]
for (final snippet in snippets) {
final autoRunsOn = snippet.autoRunOn;
final idx = autoRunsOn?.indexOf(oldId);
if (idx != null && idx != -1) {
final newAutoRunsOn = List<String>.from(autoRunsOn ?? []);
newAutoRunsOn[idx] = newId;
final newSnippet = snippet.copyWith(autoRunOn: newAutoRunsOn);
SnippetStore.instance.update(snippet, newSnippet);
}
}
// Replace ids in [Container]
final dockerHost = container.fetch(oldId);
if (dockerHost != null) {
container.remove(oldId);
container.set(newId, dockerHost);
}
}
if (srvOrderChanged) {
SettingStore.instance.serverOrder.put(srvOrder);
}
}
} }

View File

@@ -1,98 +1,93 @@
import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/app/menu/server_func.dart'; import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/model/app/server_detail_card.dart'; import 'package:server_box/data/model/app/server_detail_card.dart';
import 'package:server_box/data/model/ssh/virtual_key.dart'; import 'package:server_box/data/model/ssh/virtual_key.dart';
import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/res/default.dart'; import 'package:server_box/data/res/default.dart';
class SettingStore extends PersistentStore { class SettingStore extends HiveStore {
SettingStore._() : super('setting'); SettingStore._() : super('setting');
static final instance = SettingStore._(); static final instance = SettingStore._();
// ------BEGIN------
//
// These settings are not displayed in the settings page
// You can edit them in the settings json editor (by long press the settings
// item in the drawer of the home page)
/// Discussion #146
late final serverTabUseOldUI = property('serverTabUseOldUI', false);
/// Time out for server connect and more... /// Time out for server connect and more...
late final timeout = property('timeOut', 5); late final timeout = propertyDefault('timeOut', 5);
/// Record history of SFTP path and etc. /// Record history of SFTP path and etc.
late final recordHistory = property('recordHistory', true); late final recordHistory = propertyDefault('recordHistory', true);
/// Lanch page idx /// Lanch page idx
// late final launchPage = property('launchPage', Defaults.launchPageIdx); // late final launchPage = property('launchPage', Defaults.launchPageIdx);
/// Disk view: amount / IO /// Disk view: amount / IO
late final serverTabPreferDiskAmount = property( late final serverTabPreferDiskAmount = propertyDefault(
'serverTabPreferDiskAmount', 'serverTabPreferDiskAmount',
false, false,
); );
// ------END------
/// Bigger for bigger font size /// Bigger for bigger font size
/// 1.0 means 100% /// 1.0 means 100%
/// Warning: This may cause some UI issues /// Warning: This may cause some UI issues
late final textFactor = property('textFactor', 1.0); late final textFactor = propertyDefault('textFactor', 1.0);
/// The seed of color scheme /// The seed of color scheme
late final colorSeed = property('primaryColor', 4287106639); late final colorSeed = propertyDefault('primaryColor', 4287106639);
late final serverStatusUpdateInterval = property( late final serverStatusUpdateInterval = propertyDefault(
'serverStatusUpdateInterval', 'serverStatusUpdateInterval',
Defaults.updateInterval, Defaults.updateInterval,
); );
// Max retry count when connect to server // Max retry count when connect to server
late final maxRetryCount = property('maxRetryCount', 2); late final maxRetryCount = propertyDefault('maxRetryCount', 2);
// Night mode: 0 -> auto, 1 -> light, 2 -> dark, 3 -> AMOLED, 4 -> AUTO-AMOLED // Night mode: 0 -> auto, 1 -> light, 2 -> dark, 3 -> AMOLED, 4 -> AUTO-AMOLED
late final themeMode = property('themeMode', 0); late final themeMode = propertyDefault('themeMode', 0);
// Font file path // Font file path
late final fontPath = property('fontPath', ''); late final fontPath = propertyDefault('fontPath', '');
// Backgroud running (Android) // Backgroud running (Android)
late final bgRun = property('bgRun', isAndroid); late final bgRun = propertyDefault('bgRun', isAndroid);
// Server order // Server order
late final serverOrder = listProperty<String>('serverOrder', []); late final serverOrder = listProperty<String>('serverOrder');
late final snippetOrder = listProperty<String>('snippetOrder', []); late final snippetOrder = listProperty<String>('snippetOrder');
// Server details page cards order // Server details page cards order
late final detailCardOrder = listProperty( late final detailCardOrder = listProperty(
'detailCardOrder', 'detailCardOrder',
ServerDetailCards.values.map((e) => e.name).toList(), defaultValue: ServerDetailCards.values.map((e) => e.name).toList(),
); );
// SSH term font size // SSH term font size
late final termFontSize = property('termFontSize', 13.0); late final termFontSize = propertyDefault('termFontSize', 13.0);
// Locale // Locale
late final locale = property('locale', ''); late final locale = propertyDefault('locale', '');
// SSH virtual key (ctrl | alt) auto turn off // SSH virtual key (ctrl | alt) auto turn off
late final sshVirtualKeyAutoOff = property('sshVirtualKeyAutoOff', true); late final sshVirtualKeyAutoOff = propertyDefault(
'sshVirtualKeyAutoOff',
true,
);
late final editorFontSize = property('editorFontSize', 12.5); late final editorFontSize = propertyDefault('editorFontSize', 12.5);
// Editor theme // Editor theme
late final editorTheme = property('editorTheme', Defaults.editorTheme); late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
late final editorDarkTheme = late final editorDarkTheme = propertyDefault(
property('editorDarkTheme', Defaults.editorDarkTheme); 'editorDarkTheme',
Defaults.editorDarkTheme,
);
late final fullScreen = property('fullScreen', false); late final fullScreen = propertyDefault('fullScreen', false);
late final fullScreenJitter = property('fullScreenJitter', true); late final fullScreenJitter = propertyDefault('fullScreenJitter', true);
// late final fullScreenRotateQuarter = property( // late final fullScreenRotateQuarter = property(
// 'fullScreenRotateQuarter', // 'fullScreenRotateQuarter',
@@ -104,140 +99,175 @@ class SettingStore extends PersistentStore {
// TextInputType.text.index, // TextInputType.text.index,
// ); // );
late final sshVirtKeys = listProperty( late final sshVirtKeys = listProperty<int>(
'sshVirtKeys', 'sshVirtKeys',
VirtKeyX.defaultOrder.map((e) => e.index).toList(), defaultValue: VirtKeyX.defaultOrder.map((e) => e.index).toList(),
fromObj: (val) => List<int>.from(val as List),
); );
late final netViewType = property('netViewType', NetViewType.speed); late final netViewType = propertyDefault(
'netViewType',
NetViewType.speed,
fromObj: (val) => NetViewType.values.firstWhereOrNull((e) => e.name == val),
toObj: (type) => type?.name,
);
// Only valid on iOS // Only valid on iOS
late final autoUpdateHomeWidget = property('autoUpdateHomeWidget', isIOS); late final autoUpdateHomeWidget = propertyDefault(
'autoUpdateHomeWidget',
isIOS,
);
late final autoCheckAppUpdate = property('autoCheckAppUpdate', true); late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', true);
/// Display server tab function buttons on the bottom of each server card if [true] /// Display server tab function buttons on the bottom of each server card if [true]
/// ///
/// Otherwise, display them on the top of server detail page /// Otherwise, display them on the top of server detail page
late final moveServerFuncs = property('moveOutServerTabFuncBtns', false); late final moveServerFuncs = propertyDefault(
'moveOutServerTabFuncBtns',
false,
);
/// Whether use `rm -r` to delete directory on SFTP /// Whether use `rm -r` to delete directory on SFTP
late final sftpRmrDir = property('sftpRmrDir', false); late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
/// Whether use system's primary color as the app's primary color /// Whether use system's primary color as the app's primary color
late final useSystemPrimaryColor = property('useSystemPrimaryColor', false); late final useSystemPrimaryColor = propertyDefault(
'useSystemPrimaryColor',
false,
);
/// Only valid on iOS / Android / Windows /// Only valid on iOS / Android / Windows
late final useBioAuth = property('useBioAuth', false); late final useBioAuth = propertyDefault('useBioAuth', false);
/// The performance of highlight is bad /// The performance of highlight is bad
late final editorHighlight = property('editorHighlight', true); late final editorHighlight = propertyDefault('editorHighlight', true);
/// Open SFTP with last viewed path /// Open SFTP with last viewed path
late final sftpOpenLastPath = property('sftpOpenLastPath', true); late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
/// Show folders first in SFTP file browser /// Show folders first in SFTP file browser
late final sftpShowFoldersFirst = property('sftpShowFoldersFirst', true); late final sftpShowFoldersFirst = propertyDefault(
'sftpShowFoldersFirst',
true,
);
/// Show tip of suspend /// Show tip of suspend
late final showSuspendTip = property('showSuspendTip', true); late final showSuspendTip = propertyDefault('showSuspendTip', true);
/// Whether collapse UI items by default /// Whether collapse UI items by default
late final collapseUIDefault = property('collapseUIDefault', true); late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
late final serverFuncBtns = listProperty( late final serverFuncBtns = listProperty(
'serverBtns', 'serverBtns',
ServerFuncBtn.defaultIdxs, defaultValue: ServerFuncBtn.defaultIdxs,
); );
/// Docker is more popular than podman, set to `false` to use docker /// Docker is more popular than podman, set to `false` to use docker
late final usePodman = property('usePodman', false); late final usePodman = propertyDefault('usePodman', false);
/// Try to use `sudo` to run docker command /// Try to use `sudo` to run docker command
late final containerTrySudo = property('containerTrySudo', true); late final containerTrySudo = propertyDefault('containerTrySudo', true);
/// Keep previous server status when err occurs /// Keep previous server status when err occurs
late final keepStatusWhenErr = property('keepStatusWhenErr', false); late final keepStatusWhenErr = propertyDefault('keepStatusWhenErr', false);
/// Parse container stat /// Parse container stat
late final containerParseStat = property('containerParseStat', true); late final containerParseStat = propertyDefault('containerParseStat', true);
/// Auto refresh container status /// Auto refresh container status
late final contaienrAutoRefresh = property('contaienrAutoRefresh', true); late final contaienrAutoRefresh = propertyDefault(
'contaienrAutoRefresh',
true,
);
/// Use double column servers page on Desktop /// Use double column servers page on Desktop
late final doubleColumnServersPage = property( late final doubleColumnServersPage = propertyDefault(
'doubleColumnServersPage', 'doubleColumnServersPage',
true, true,
); );
/// Ignore local network device (eg: br-xxx, ovs-system...) /// Ignore local network device (eg: br-xxx, ovs-system...)
/// when building traffic view on server tab /// when building traffic view on server tab
//late final ignoreLocalNet = property('ignoreLocalNet', true); //late final ignoreLocalNet = propertyDefault('ignoreLocalNet', true);
/// Remerber pwd in memory /// Remerber pwd in memory
/// Used for [DialogX.showPwdDialog] /// Used for [DialogX.showPwdDialog]
late final rememberPwdInMem = property('rememberPwdInMem', true); late final rememberPwdInMem = propertyDefault('rememberPwdInMem', true);
/// SSH Term Theme /// SSH Term Theme
/// 0: follow app theme, 1: light, 2: dark /// 0: follow app theme, 1: light, 2: dark
late final termTheme = property('termTheme', 0); late final termTheme = propertyDefault('termTheme', 0);
/// Compatiablity for Chinese Android. /// Compatiablity for Chinese Android.
/// Set it to true, if you use Safe Keyboard on Chinese Android /// Set it to true, if you use Safe Keyboard on Chinese Android
// late final cnKeyboardComp = property('cnKeyboardComp', false); // late final cnKeyboardComp = propertyDefault('cnKeyboardComp', false);
late final lastVer = property('lastVer', 0); late final lastVer = propertyDefault('lastVer', 0);
/// Use CupertinoPageRoute for all routes /// Use CupertinoPageRoute for all routes
late final cupertinoRoute = property('cupertinoRoute', isIOS); late final cupertinoRoute = propertyDefault('cupertinoRoute', isIOS);
/// Hide title bar on desktop /// Hide title bar on desktop
late final hideTitleBar = property('hideTitleBar', isDesktop); late final hideTitleBar = propertyDefault('hideTitleBar', isDesktop);
/// Display CPU view as progress, also called as old CPU view /// Display CPU view as progress, also called as old CPU view
late final cpuViewAsProgress = property('cpuViewAsProgress', false); late final cpuViewAsProgress = propertyDefault('cpuViewAsProgress', false);
late final displayCpuIndex = property('displayCpuIndex', true); late final displayCpuIndex = propertyDefault('displayCpuIndex', true);
late final editorSoftWrap = property('editorSoftWrap', isIOS); late final editorSoftWrap = propertyDefault('editorSoftWrap', isIOS);
late final sshTermHelpShown = property('sshTermHelpShown', false); late final sshTermHelpShown = propertyDefault('sshTermHelpShown', false);
late final horizonVirtKey = property('horizonVirtKey', false); late final horizonVirtKey = propertyDefault('horizonVirtKey', false);
/// general wake lock /// general wake lock
late final generalWakeLock = property('generalWakeLock', false); late final generalWakeLock = propertyDefault('generalWakeLock', false);
/// ssh page /// ssh page
late final sshWakeLock = property('sshWakeLock', true); late final sshWakeLock = propertyDefault('sshWakeLock', true);
late final sshBgImage = propertyDefault('sshBgImage', '');
late final sshBgOpacity = propertyDefault('sshBgOpacity', 0.3);
late final sshBlurRadius = propertyDefault('sshBlurRadius', 0.0);
/// fmt: https://example.com/{DIST}-{BRIGHT}.png /// fmt: https://example.com/{DIST}-{BRIGHT}.png
late final serverLogoUrl = property('serverLogoUrl', ''); late final serverLogoUrl = propertyDefault('serverLogoUrl', '');
late final betaTest = property('betaTest', false); late final betaTest = propertyDefault('betaTest', false);
/// If it's empty, skip change window size. /// For desktop only.
/// Format: {width}x{height} /// Record the position and size of the window.
late final windowSize = property('windowSize', ''); late final windowState = property<WindowState>(
'windowState',
fromObj: (raw) =>
WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
);
late final introVer = property('introVer', 0); late final introVer = propertyDefault('introVer', 0);
late final letterCache = property('letterCache', false); late final letterCache = propertyDefault('letterCache', false);
/// Set it to `$EDITOR`, `vim` and etc. to use remote system editor in SSH terminal. /// Set it to `$EDITOR`, `vim` and etc. to use remote system editor in SSH terminal.
/// Set it empty to use local editor GUI. /// Set it empty to use local editor GUI.
late final sftpEditor = property('sftpEditor', ''); late final sftpEditor = propertyDefault('sftpEditor', '');
// Never show these settings for users /// Preferred terminal emulator command on desktop
// late final desktopTerminal = propertyDefault(
// ------BEGIN------ 'desktopTerminal',
'x-terminal-emulator',
);
/// Run foreground service on Android, if the SSH terminal is running
late final fgService = propertyDefault('fgService', false);
/// Close the editor after saving
late final closeAfterSave = propertyDefault('closeAfterSave', false);
/// Version of store db /// Version of store db
late final storeVersion = property('storeVersion', 0); late final storeVersion = propertyDefault('storeVersion', 0);
/// Have notified user for notificaiton permission or not /// Have notified user for notificaiton permission or not
late final noNotiPerm = property('noNotiPerm', false); late final noNotiPerm = propertyDefault('noNotiPerm', false);
// ------END------
} }

View File

@@ -2,30 +2,54 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
class SnippetStore extends PersistentStore { class SnippetStore extends HiveStore {
SnippetStore._() : super('snippet'); SnippetStore._() : super('snippet');
static final instance = SnippetStore._(); static final instance = SnippetStore._();
void put(Snippet snippet) { void put(Snippet snippet) {
box.put(snippet.name, snippet); set(snippet.name, snippet);
box.updateLastModified();
} }
List<Snippet> fetch() { List<Snippet> fetch() {
final keys = box.keys; final ss = <Snippet>{};
final ss = <Snippet>[]; for (final key in keys()) {
for (final key in keys) { final s = get<Snippet>(
final s = box.get(key); key,
if (s != null && s is Snippet) { fromObj: (val) {
if (val is Snippet) return val;
if (val is Map<dynamic, dynamic>) {
final map = val.toStrDynMap;
if (map == null) return null;
try {
final snippet = Snippet.fromJson(map as Map<String, dynamic>);
put(snippet);
return snippet;
} catch (e) {
dprint('Parsing Snippet from JSON', e);
}
}
return null;
},
);
if (s != null) {
ss.add(s); ss.add(s);
} }
} }
return ss; return ss.toList();
} }
void delete(Snippet s) { void delete(Snippet s) {
box.delete(s.name); remove(s.name);
box.updateLastModified();
} }
void update(Snippet old, Snippet newInfo) {
if (!have(old)) {
throw Exception('Old snippet: $old not found');
}
delete(old);
put(newInfo);
}
bool have(Snippet s) => get(s.name) != null;
} }

1582
lib/generated/l10n/l10n.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,776 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'l10n.dart';
// ignore_for_file: type=lint
/// The translations for German (`de`).
class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get aboutThanks =>
'Vielen Dank an die folgenden Personen, die daran teilgenommen haben.\n';
@override
String get acceptBeta => 'Akzeptieren Sie Testversion-Updates';
@override
String get addSystemPrivateKeyTip =>
'Derzeit haben Sie keinen privaten Schlüssel, fügen Sie den Schlüssel hinzu, der mit dem System geliefert wird (~/.ssh/id_rsa)?';
@override
String get added2List => 'Zur Aufgabenliste hinzugefügt';
@override
String get addr => 'Adresse';
@override
String get alreadyLastDir => 'Bereits im letzten Verzeichnis.';
@override
String get authFailTip =>
'Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.';
@override
String get autoBackupConflict =>
'Es kann nur eine automatische Sicherung gleichzeitig aktiviert werden.';
@override
String get autoConnect => 'Automatisch verbinden';
@override
String get autoRun => 'Automatischer Start';
@override
String get autoUpdateHomeWidget => 'Home-Widget automatisch aktualisieren';
@override
String get backupTip =>
'Das Backup wird nur einfach verschlüsselt.\nBitte bewahre die Datei sicher auf.';
@override
String get backupVersionNotMatch =>
'Die Backup-Version stimmt nicht überein.';
@override
String get battery => 'Batterie';
@override
String get bgRun => 'Hintergrundaktualisierung';
@override
String get bgRunTip =>
'Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".';
@override
String get closeAfterSave => 'Speichern und schließen';
@override
String get cmd => 'Command';
@override
String get collapseUITip =>
'Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht';
@override
String get conn => 'Verbindung';
@override
String get container => 'Container';
@override
String get containerTrySudoTip =>
'Zum Beispiel: In der App ist der Benutzer auf aaa eingestellt, aber Docker ist unter dem Root-Benutzer installiert. In diesem Fall müssen Sie diese Option aktivieren';
@override
String get convert => 'Konvertieren';
@override
String get copyPath => 'Pfad kopieren';
@override
String get cpuViewAsProgressTip =>
'Zeigen Sie die Auslastung jedes CPUs in einem Fortschrittsbalken-Stil an (alter Stil)';
@override
String get cursorType => 'Cursor-Typ';
@override
String get customCmd => 'Benutzerdefinierte Befehle';
@override
String get customCmdDocUrl =>
'https://github.com/lollipopkit/flutter_server_box/wiki#custom-commands';
@override
String get customCmdHint => '\"Befehlsname\": \"Befehl\"';
@override
String get decode => 'Decode';
@override
String get decompress => 'Dekomprimieren';
@override
String get deleteServers => 'Batch-Löschung von Servern';
@override
String get desktopTerminalTip =>
'Befehl zum Öffnen des Terminal-Emulators beim Starten von SSH-Sitzungen.';
@override
String get dirEmpty => 'Stelle sicher, dass der Ordner leer ist.';
@override
String get disconnected => 'Disconnected';
@override
String get disk => 'Festplatte';
@override
String get diskHealth => 'Festplattengesundheit';
@override
String get diskIgnorePath => 'Pfad für Datenträger ignorieren';
@override
String get displayCpuIndex => 'Zeigen Sie den CPU-Index an';
@override
String dl2Local(Object fileName) {
return 'Datei \"$fileName\" herunterladen?';
}
@override
String get dockerEmptyRunningItems =>
'Es gibt keine laufenden Container.\nDas könnte daran liegen:\n- Der Docker-Installationsbenutzer ist nicht mit dem in der App konfigurierten Benutzernamen identisch.\n- Die Umgebungsvariable DOCKER_HOST wurde nicht korrekt gelesen. Sie können sie ermitteln, indem Sie `echo \$DOCKER_HOST` im Terminal ausführen.';
@override
String dockerImagesFmt(Object count) {
return '$count Image(s)';
}
@override
String get dockerNotInstalled => 'Docker ist nicht installiert';
@override
String dockerStatusRunningAndStoppedFmt(
Object runningCount,
Object stoppedCount,
) {
return '$runningCount aktiv, $stoppedCount container gestoppt.';
}
@override
String dockerStatusRunningFmt(Object count) {
return '$count Container aktiv';
}
@override
String get doubleColumnMode => 'Doppelspaltiger Modus';
@override
String get doubleColumnTip =>
'Diese Option aktiviert nur die Funktion, ob sie tatsächlich aktiviert werden kann, hängt auch von der Breite des Geräts ab';
@override
String get editVirtKeys => 'Virtuelle Tasten bearbeiten';
@override
String get editor => 'Editor';
@override
String get editorHighlightTip =>
'Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.';
@override
String get emulator => 'Emulator';
@override
String get encode => 'Encode';
@override
String get envVars => 'Umgebungsvariable';
@override
String get experimentalFeature => 'Experimentelles Feature';
@override
String get extraArgs => 'Extra args';
@override
String get fallbackSshDest => 'SSH-Fallback-Ziel';
@override
String get fdroidReleaseTip =>
'Wenn Sie diese App von F-Droid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.';
@override
String get fgService => 'Vordergrund-Dienst';
@override
String get fgServiceTip =>
'Nach dem Einschalten kann es bei einigen Gerätemodellen zu Abstürzen kommen. Das Ausschalten kann bei einigen Modellen dazu führen, dass SSH-Verbindungen im Hintergrund nicht aufrechterhalten werden können. Bitte erlauben Sie ServerBox in den Systemeinstellungen Benachrichtigungsrechte, Hintergrundausführung und Selbstaktivierung.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Datei \'$file\' ist zu groß $size, max $sizeMax';
}
@override
String get followSystem => 'System verfolgen';
@override
String get font => 'Schriftarten';
@override
String get fontSize => 'Schriftgröße';
@override
String get force => 'freiwillig';
@override
String get fullScreen => 'Vollbildmodus';
@override
String get fullScreenJitter => 'Jitter im Vollbildmodus';
@override
String get fullScreenJitterHelp => 'Einbrennen des Bildschirms verhindern';
@override
String get fullScreenTip =>
'Soll der Vollbildmodus aktiviert werden, wenn das Gerät in den Quermodus gedreht wird? Diese Option gilt nur für die Server-Registerkarte.';
@override
String get goBackQ => 'Zurückkommen?';
@override
String get goto => 'Pfad öffnen';
@override
String get hideTitleBar => 'Titelleiste ausblenden';
@override
String get highlight => 'Code highlight';
@override
String get homeWidgetUrlConfig => 'Home-Widget-Link konfigurieren';
@override
String get host => 'Host';
@override
String httpFailedWithCode(Object code) {
return 'Anfrage fehlgeschlagen, Statuscode: $code';
}
@override
String get ignoreCert => 'Zertifikat ignorieren';
@override
String get image => 'Image';
@override
String get imagesList => 'Images';
@override
String get init => 'Initialisieren';
@override
String get inner => 'Eingebaut';
@override
String get install => 'install';
@override
String get installDockerWithUrl =>
'Bitte installiere docker zuerst. https://docs.docker.com/engine/install';
@override
String get invalid => 'Ungültig';
@override
String get jumpServer => 'Server springen';
@override
String get keepForeground => 'Stelle sicher, dass die App geöffnet bleibt.';
@override
String get keepStatusWhenErr => 'Den letzten Serverstatus beibehalten';
@override
String get keepStatusWhenErrTip =>
'Nur im Fehlerfall während der Ausführung des Skripts';
@override
String get keyAuth => 'Schlüsselauthentifzierung';
@override
String get letterCache => 'Buchstaben-Caching';
@override
String get letterCacheTip =>
'Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.';
@override
String get license => 'Lizenzen';
@override
String get location => 'Standort';
@override
String get loss => 'loss';
@override
String madeWithLove(Object myGithub) {
return 'Erstellt mit ❤️ von $myGithub';
}
@override
String get manual => 'Handbuch';
@override
String get max => 'max';
@override
String get maxRetryCount => 'Anzahl an Verbindungsversuchen';
@override
String get maxRetryCountEqual0 =>
'Unbegrenzte Verbindungsversuche zum Server';
@override
String get min => 'min';
@override
String get mission => 'Mission';
@override
String get more => 'Mehr';
@override
String get moveOutServerFuncBtnsHelp =>
'Ein: kann unter jeder Karte auf der Registerkarte \"Server\" angezeigt werden. Aus: kann oben auf der Seite \"Serverdetails\" angezeigt werden.';
@override
String get ms => 'ms';
@override
String get 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.';
@override
String get needRestart => 'App muss neugestartet werden';
@override
String get net => 'Netzwerk';
@override
String get netViewType => 'Netzwerkansicht Typ';
@override
String get newContainer => 'Neuer Container';
@override
String get noLineChart => 'Verwenden Sie keine Liniendiagramme';
@override
String get noLineChartForCpu => 'Verwenden Sie keine Liniendiagramme für CPU';
@override
String get noPrivateKeyTip =>
'Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.';
@override
String get noPromptAgain => 'Nicht mehr nachfragen';
@override
String get node => 'Knoten';
@override
String get notAvailable => 'Nicht verfügbar';
@override
String get onServerDetailPage => 'in Detailansicht des Servers';
@override
String get onlyOneLine => 'Nur als eine Zeile anzeigen (scrollbar)';
@override
String get onlyWhenCoreBiggerThan8 =>
'Wirksam nur, wenn die Anzahl der Kerne > 8 ist.';
@override
String get openLastPath => 'Öffnen Sie den letzten Pfad';
@override
String get openLastPathTip =>
'Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang';
@override
String get parseContainerStatsTip =>
'Das Analysieren des Belegungsstatus durch Docker ist relativ langsam';
@override
String percentOfSize(Object percent, Object size) {
return '$percent% von $size';
}
@override
String get permission => 'Berechtigungen';
@override
String get pingAvg => 'Avg:';
@override
String get pingInputIP => 'Bitte gib eine Ziel-IP/Domain ein.';
@override
String get pingNoServer =>
'Kein Server zum Anpingen.\nBitte füge einen Server hinzu.';
@override
String get pkg => 'Pkg';
@override
String get plugInType => 'Einfügetyp';
@override
String get port => 'Port';
@override
String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen';
@override
String get preview => 'Vorschau';
@override
String get privateKey => 'Private Key';
@override
String get process => 'Prozess';
@override
String get pushToken => 'Push Token';
@override
String get pveIgnoreCertTip =>
'Nicht empfohlen, Achten Sie auf Sicherheitsrisiken! Wenn Sie das Standardzertifikat von PVE verwenden, müssen Sie diese Option aktivieren.';
@override
String get pveLoginFailed =>
'Anmeldung fehlgeschlagen. Kann nicht mit Benutzername/Passwort aus der Serverkonfiguration angemeldet werden, um sich über Linux PAM anzumelden.';
@override
String get pveVersionLow =>
'Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.';
@override
String get pwd => 'Passwort';
@override
String get read => 'Lesen';
@override
String get reboot => 'Neustart';
@override
String get rememberPwdInMem => 'Passwort im Speicher behalten';
@override
String get rememberPwdInMemTip => 'Für Container, Aufhängen usw.';
@override
String get rememberWindowSize => 'Fenstergröße merken';
@override
String get remotePath => 'Entfernte Pfade';
@override
String get restart => 'Neustart';
@override
String get result => 'Result';
@override
String get rotateAngel => 'Rotationswinkel';
@override
String get route => 'Routen';
@override
String get run => 'Ausführen';
@override
String get running => 'läuft';
@override
String get sameIdServerExist =>
'Ein Server mit derselben ID existiert bereits';
@override
String get save => 'Speichern';
@override
String get saved => 'Gerettet';
@override
String get second => 's';
@override
String get sensors => 'Sensor';
@override
String get sequence => 'Sequenz';
@override
String get server => 'Server';
@override
String get serverDetailOrder => 'Reihenfolge der Widgets auf der Detailseite';
@override
String get serverFuncBtns => 'Server-Funktionsschaltflächen';
@override
String get serverOrder => 'Server-Bestellung';
@override
String get sftpDlPrepare => 'Verbindung vorbereiten...';
@override
String get 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).';
@override
String get sftpRmrDirSummary =>
'Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.';
@override
String get sftpSSHConnected => 'SFTP Verbunden';
@override
String get sftpShowFoldersFirst => 'Ordner zuerst anzeigen';
@override
String get showDistLogo => 'Distributionslogo anzeigen';
@override
String get shutdown => 'Abschaltung';
@override
String get size => 'Größe';
@override
String get snippet => 'Snippet';
@override
String get softWrap => 'Weicher Umbruch';
@override
String get specifyDev => 'Gerät angeben';
@override
String get specifyDevTip =>
'Zum Beispiel bezieht sich die Standard-Netzwerkverkehrsstatistik auf alle Geräte. Hier können Sie ein bestimmtes Gerät angeben.';
@override
String get speed => 'Tempo';
@override
String spentTime(Object time) {
return 'Benötigte Zeit: $time';
}
@override
String get sshTermHelp =>
'Wenn das Terminal scrollbar ist, kann durch horizontales Ziehen Text ausgewählt werden. Durch Klicken auf die Tastentaste wird die Tastatur ein- oder ausgeschaltet. Das Dateisymbol öffnet den aktuellen Pfad SFTP. Die Zwischenablage-Schaltfläche kopiert den Inhalt, wenn Text ausgewählt ist, und fügt Inhalte aus der Zwischenablage in das Terminal ein, wenn kein Text ausgewählt ist und Inhalte in der Zwischenablage vorhanden sind. Das Codesymbol fügt Code-Schnipsel ins Terminal ein und führt sie aus.';
@override
String sshTip(Object url) {
return 'Diese Funktion befindet sich jetzt in der Experimentierphase.\n\nBitte melde Bugs auf $url oder mach mit bei der Entwicklung.';
}
@override
String get sshVirtualKeyAutoOff =>
'Automatische Umschaltung der virtuellen Tasten';
@override
String get start => 'Start';
@override
String get stat => 'Statistik';
@override
String get stats => 'Statistik';
@override
String get stop => 'Stop';
@override
String get stopped => 'Ausgelaufen';
@override
String get storage => 'Speicher';
@override
String get supportFmtArgs =>
'Die folgenden Formatierungsparameter werden unterstützt:';
@override
String get suspend => 'Suspend';
@override
String get suspendTip =>
'Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.';
@override
String switchTo(Object val) {
return 'Wechseln zu $val';
}
@override
String get sync => 'Sync';
@override
String get syncTip =>
'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
@override
String get system => 'Systeme';
@override
String get tag => 'Tags';
@override
String get temperature => 'Temperatur';
@override
String get termFontSizeTip =>
'Diese Einstellung beeinflusst die Größe des Terminals (Breite und Höhe). Sie können die Terminalseite zoomen, um die Schriftgröße der aktuellen Sitzung anzupassen.';
@override
String get terminal => 'Terminal';
@override
String get test => 'Prüfung';
@override
String get textScaler => 'Skalierung der Schriftart';
@override
String get textScalerTip =>
'1.0 => 100% (Originalgröße), funktioniert nur auf der Serverseite Teil der Schrift, nicht empfohlen zu ändern.';
@override
String get theme => 'Themen';
@override
String get time => 'Zeit';
@override
String get times => 'x';
@override
String get total => 'Total';
@override
String get traffic => 'Durchflussmenge';
@override
String get trySudo => 'Versuche es mit sudo';
@override
String get ttl => 'TTL';
@override
String get unknown => 'Unbekannt';
@override
String get unkownConvertMode => 'Unbekannter Konvertierungsmodus';
@override
String get update => 'Update';
@override
String get updateIntervalEqual0 =>
'Wenn du den Wert 0 einstellst, wird nicht automatisch aktualisiert.\nDer CPU-Status kann nicht berechnet werden.';
@override
String get updateServerStatusInterval =>
'Aktualisierungsintervall des Serverstatus';
@override
String get upload => 'Hochladen';
@override
String get upsideDown => 'Upside Down';
@override
String get uptime => 'Betriebszeit';
@override
String get useCdn => 'Verwenden von CDN';
@override
String get useCdnTip =>
'Nicht-chinesischen Benutzern wird die Verwendung eines CDN empfohlen. Möchten Sie es verwenden?';
@override
String get useNoPwd => 'Es wird kein Passwort verwendet';
@override
String get usePodmanByDefault => 'Standardmäßige Verwendung von Podman';
@override
String get used => 'Gebraucht';
@override
String get view => 'Ansicht';
@override
String get viewErr => 'Fehler anzeigen';
@override
String get virtKeyHelpClipboard =>
'In die Zwischenablage kopieren, wenn das ausgewählte Terminal nicht leer ist, andernfalls den Inhalt der Zwischenablage in das Terminal einfügen.';
@override
String get virtKeyHelpIME => 'Tastatur ein-/ausschalten';
@override
String get virtKeyHelpSFTP => 'Aktuelles Verzeichnis in SFTP öffnen.';
@override
String get waitConnection =>
'Bitte warte, bis die Verbindung hergestellt wurde.';
@override
String get wakeLock => 'Wach halten';
@override
String get watchNotPaired => 'Keine gekoppelte Apple Watch';
@override
String get webdavSettingEmpty => 'Webdav-Einstellungen sind leer';
@override
String get whenOpenApp => 'Beim Öffnen der App';
@override
String get wolTip =>
'Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.';
@override
String get write => 'Schreiben';
@override
String get writeScriptFailTip =>
'Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.';
@override
String get writeScriptTip =>
'Nach der Verbindung mit dem Server wird ein Skript in ~/.config/server_box geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.';
}

View File

@@ -0,0 +1,770 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'l10n.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get aboutThanks =>
'Thanks to the following people who participated in.';
@override
String get acceptBeta => 'Accept beta version updates';
@override
String get addSystemPrivateKeyTip =>
'Currently private keys don\'t exist, do you want to add the one that comes with the system (~/.ssh/id_rsa)?';
@override
String get added2List => 'Added to task list';
@override
String get addr => 'Address';
@override
String get alreadyLastDir => 'Already in last directory.';
@override
String get authFailTip =>
'Authentication failed, please check whether credentials are correct';
@override
String get autoBackupConflict =>
'Only one automatic backup can be turned on at the same time.';
@override
String get autoConnect => 'Auto connect';
@override
String get autoRun => 'Auto run';
@override
String get autoUpdateHomeWidget => 'Automatic home widget update';
@override
String get backupTip =>
'The exported data is weakly encrypted. \nPlease keep it safe.';
@override
String get backupVersionNotMatch => 'Backup version is not match.';
@override
String get battery => 'Battery';
@override
String get bgRun => 'Run in background';
@override
String get 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\".';
@override
String get closeAfterSave => 'Save and close';
@override
String get cmd => 'Command';
@override
String get collapseUITip =>
'Whether to collapse long lists present in the UI by default';
@override
String get conn => 'Connection';
@override
String get container => 'Container';
@override
String get containerTrySudoTip =>
'For example: In the app, the user is set to aaa, but Docker is installed under the root user. In this case, you need to enable this option.';
@override
String get convert => 'Convert';
@override
String get copyPath => 'Copy path';
@override
String get cpuViewAsProgressTip =>
'Display the usage of each CPU in a progress bar style (old style)';
@override
String get cursorType => 'Cursor type';
@override
String get customCmd => 'Custom commands';
@override
String get customCmdDocUrl =>
'https://github.com/lollipopkit/flutter_server_box/wiki#custom-commands';
@override
String get customCmdHint => '\"Command Name\": \"Command\"';
@override
String get decode => 'Decode';
@override
String get decompress => 'Decompress';
@override
String get deleteServers => 'Batch delete servers';
@override
String get desktopTerminalTip =>
'Command used to open the terminal emulator when launching SSH sessions.';
@override
String get dirEmpty => 'Make sure the folder is empty.';
@override
String get disconnected => 'Disconnected';
@override
String get disk => 'Disk';
@override
String get diskHealth => 'Disk Health';
@override
String get diskIgnorePath => 'Ignore path for disk';
@override
String get displayCpuIndex => 'Display CPU index';
@override
String dl2Local(Object fileName) {
return 'Download $fileName to local?';
}
@override
String get dockerEmptyRunningItems =>
'There are no running containers.\nThis could be because:\n- The Docker installation user is not the same as the username configured within the App.\n- The environment variable DOCKER_HOST was not read correctly. You can get it by running `echo \$DOCKER_HOST` in the terminal.';
@override
String dockerImagesFmt(Object count) {
return '$count images';
}
@override
String get dockerNotInstalled => 'Docker not installed';
@override
String dockerStatusRunningAndStoppedFmt(
Object runningCount,
Object stoppedCount,
) {
return '$runningCount running, $stoppedCount container stopped.';
}
@override
String dockerStatusRunningFmt(Object count) {
return '$count container running.';
}
@override
String get doubleColumnMode => 'Double column mode';
@override
String get doubleColumnTip =>
'This option only enables the feature, whether it can actually be enabled depends on the width of the device';
@override
String get editVirtKeys => 'Edit virtual keys';
@override
String get editor => 'Editor';
@override
String get editorHighlightTip =>
'The current code highlighting performance is not ideal and can be optionally turned off to improve.';
@override
String get emulator => 'Emulator';
@override
String get encode => 'Encode';
@override
String get envVars => 'Environment variable';
@override
String get experimentalFeature => 'Experimental feature';
@override
String get extraArgs => 'Extra arguments';
@override
String get fallbackSshDest => 'Fallback SSH destination';
@override
String get fdroidReleaseTip =>
'If you downloaded this app from F-Droid, it is recommended to turn off this option.';
@override
String get fgService => 'Foreground Service';
@override
String get fgServiceTip =>
'After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'File \'$file\' too large $size, max $sizeMax';
}
@override
String get followSystem => 'Follow system';
@override
String get font => 'Font';
@override
String get fontSize => 'Font size';
@override
String get force => 'Force';
@override
String get fullScreen => 'Full screen mode';
@override
String get fullScreenJitter => 'Full screen jitter';
@override
String get fullScreenJitterHelp => 'To avoid screen burn-in';
@override
String get fullScreenTip =>
'Should full-screen mode be enabled when the device is rotated to landscape mode? This option only applies to the server tab.';
@override
String get goBackQ => 'Go back?';
@override
String get goto => 'Go to';
@override
String get hideTitleBar => 'Hide title bar';
@override
String get highlight => 'Code highlighting';
@override
String get homeWidgetUrlConfig => 'Config home widget url';
@override
String get host => 'Host';
@override
String httpFailedWithCode(Object code) {
return 'request failed, status code: $code';
}
@override
String get ignoreCert => 'Ignore certificate';
@override
String get image => 'Image';
@override
String get imagesList => 'Images list';
@override
String get init => 'Initialize';
@override
String get inner => 'Inner';
@override
String get install => 'install';
@override
String get installDockerWithUrl =>
'Please https://docs.docker.com/engine/install docker first.';
@override
String get invalid => 'Invalid';
@override
String get jumpServer => 'Jump server';
@override
String get keepForeground => 'Keep app foreground!';
@override
String get keepStatusWhenErr => 'Preserve the last server state';
@override
String get keepStatusWhenErrTip =>
'Only in the event of an error during script execution';
@override
String get keyAuth => 'Key Auth';
@override
String get letterCache => 'Letter caching';
@override
String get letterCacheTip =>
'Recommended to disable, but after disabling, it will be impossible to input CJK characters.';
@override
String get license => 'License';
@override
String get location => 'Location';
@override
String get loss => 'loss';
@override
String madeWithLove(Object myGithub) {
return 'Made with ❤️ by $myGithub';
}
@override
String get manual => 'Manual';
@override
String get max => 'max';
@override
String get maxRetryCount => 'Number of server reconnections';
@override
String get maxRetryCountEqual0 => 'Will retry again and again.';
@override
String get min => 'min';
@override
String get mission => 'Mission';
@override
String get more => 'More';
@override
String get moveOutServerFuncBtnsHelp =>
'On: can be displayed below each card on the Server Tab page. Off: can be displayed at the top of the Server Details page.';
@override
String get ms => 'ms';
@override
String get 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.';
@override
String get needRestart => 'App needs to be restarted';
@override
String get net => 'Network';
@override
String get netViewType => 'Network view type';
@override
String get newContainer => 'New container';
@override
String get noLineChart => 'Do not use line charts';
@override
String get noLineChartForCpu => 'Do not use line charts for CPU';
@override
String get noPrivateKeyTip =>
'The private key does not exist, it may have been deleted or there is a configuration error.';
@override
String get noPromptAgain => 'Do not prompt again';
@override
String get node => 'Node';
@override
String get notAvailable => 'Unavailable';
@override
String get onServerDetailPage => 'On server detail page';
@override
String get onlyOneLine => 'Only display as one line (scrollable)';
@override
String get onlyWhenCoreBiggerThan8 =>
'Works only when the number of cores is greater than 8';
@override
String get openLastPath => 'Open the last path';
@override
String get openLastPathTip =>
'Different servers will have different logs, and the log is the path to the exit';
@override
String get parseContainerStatsTip =>
'Parsing the occupancy status of Docker is relatively slow.';
@override
String percentOfSize(Object percent, Object size) {
return '$percent% of $size';
}
@override
String get permission => 'Permissions';
@override
String get pingAvg => 'Avg:';
@override
String get pingInputIP => 'Please input a target IP / domain.';
@override
String get pingNoServer =>
'No server to ping.\nPlease add a server in server tab.';
@override
String get pkg => 'Pkg';
@override
String get plugInType => 'Insertion Type';
@override
String get port => 'Port';
@override
String get preferDiskAmount => 'Prioritize displaying disk capacity';
@override
String get preview => 'Preview';
@override
String get privateKey => 'Private Key';
@override
String get process => 'Process';
@override
String get pushToken => 'Push token';
@override
String get pveIgnoreCertTip =>
'Not recommended to enable, beware of security risks! If you are using the default certificate from PVE, you need to enable this option.';
@override
String get pveLoginFailed =>
'Login failed. Unable to authenticate with username/password from server configuration for Linux PAM login.';
@override
String get pveVersionLow =>
'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.';
@override
String get pwd => 'Password';
@override
String get read => 'Read';
@override
String get reboot => 'Reboot';
@override
String get rememberPwdInMem => 'Remember password in memory';
@override
String get rememberPwdInMemTip => 'Used for containers, suspending, etc.';
@override
String get rememberWindowSize => 'Remember window size';
@override
String get remotePath => 'Remote path';
@override
String get restart => 'Restart';
@override
String get result => 'Result';
@override
String get rotateAngel => 'Rotation angle';
@override
String get route => 'Routing';
@override
String get run => 'Run';
@override
String get running => 'Running';
@override
String get sameIdServerExist => 'A server with the same ID already exists';
@override
String get save => 'Save';
@override
String get saved => 'Saved';
@override
String get second => 's';
@override
String get sensors => 'Sensor';
@override
String get sequence => 'Sequence';
@override
String get server => 'Server';
@override
String get serverDetailOrder => 'Detail page widget order';
@override
String get serverFuncBtns => 'Server function buttons';
@override
String get serverOrder => 'Server order';
@override
String get sftpDlPrepare => 'Preparing to connect...';
@override
String get sftpEditorTip =>
'If empty, use the built-in file editor of the app. If a value is present, use the remote servers editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).';
@override
String get sftpRmrDirSummary => 'Use `rm -r` to delete a folder in SFTP.';
@override
String get sftpSSHConnected => 'SFTP Connected';
@override
String get sftpShowFoldersFirst => 'Display folders first';
@override
String get showDistLogo => 'Show distribution logo';
@override
String get shutdown => 'Shutdown';
@override
String get size => 'Size';
@override
String get snippet => 'Snippet';
@override
String get softWrap => 'Soft wrap';
@override
String get specifyDev => 'Specify device';
@override
String get specifyDevTip =>
'For example, network traffic statistics are by default for all devices. You can specify a particular device here.';
@override
String get speed => 'Speed';
@override
String spentTime(Object time) {
return 'Spent time: $time';
}
@override
String get sshTermHelp =>
'When the terminal is scrollable, dragging horizontally can select text. Clicking the keyboard button turns the keyboard on/off. The file icon opens the current path SFTP. The clipboard button copies the content when text is selected, and pastes content from the clipboard into the terminal when no text is selected and there is content on the clipboard. The code icon pastes code snippets into the terminal and executes them.';
@override
String sshTip(Object url) {
return 'This function is now in the experimental stage.\n\nPlease report bugs on $url or join our development.';
}
@override
String get sshVirtualKeyAutoOff => 'Auto switching of virtual keys';
@override
String get start => 'Start';
@override
String get stat => 'Statistics';
@override
String get stats => 'Statistics';
@override
String get stop => 'Stop';
@override
String get stopped => 'Stopped';
@override
String get storage => 'Storage';
@override
String get supportFmtArgs =>
'The following formatting parameters are supported:';
@override
String get suspend => 'Suspend';
@override
String get suspendTip =>
'The suspend function requires root permission and systemd support.';
@override
String switchTo(Object val) {
return 'Switch to $val';
}
@override
String get sync => 'Sync';
@override
String get syncTip =>
'A restart may be required for some changes to take effect.';
@override
String get system => 'System';
@override
String get tag => 'Tags';
@override
String get temperature => 'Temperature';
@override
String get termFontSizeTip =>
'This setting will affect the terminal size (width and height). You can zoom in on the terminal page to adjust the font size of the current session.';
@override
String get terminal => 'Terminal';
@override
String get test => 'Test';
@override
String get textScaler => 'Text scaler';
@override
String get textScalerTip =>
'1.0 => 100% (original size), only works on server page part of the font, not recommended to change.';
@override
String get theme => 'Theme';
@override
String get time => 'Time';
@override
String get times => 'Times';
@override
String get total => 'Total';
@override
String get traffic => 'Traffic';
@override
String get trySudo => 'Try using sudo';
@override
String get ttl => 'TTL';
@override
String get unknown => 'Unknown';
@override
String get unkownConvertMode => 'Unknown conversion mode';
@override
String get update => 'Update';
@override
String get updateIntervalEqual0 =>
'You set to 0, will not update automatically.\nCan\'t calculate CPU status.';
@override
String get updateServerStatusInterval => 'Server status update interval';
@override
String get upload => 'Upload';
@override
String get upsideDown => 'Upside Down';
@override
String get uptime => 'Uptime';
@override
String get useCdn => 'Using CDN';
@override
String get useCdnTip =>
'Non-Chinese users are recommended to use CDN. Would you like to use it?';
@override
String get useNoPwd => 'No password will be used';
@override
String get usePodmanByDefault => 'Use Podman by default';
@override
String get used => 'Used';
@override
String get view => 'View';
@override
String get viewErr => 'See error';
@override
String get virtKeyHelpClipboard =>
'Copy to the clipboard if the selected terminal is not empty, otherwise paste the content of the clipboard to the terminal.';
@override
String get virtKeyHelpIME => 'Turn on/off the keyboard';
@override
String get virtKeyHelpSFTP => 'Open current directory in SFTP.';
@override
String get waitConnection =>
'Please wait for the connection to be established.';
@override
String get wakeLock => 'Keep awake';
@override
String get watchNotPaired => 'No paired Apple Watch';
@override
String get webdavSettingEmpty => 'WebDav setting is empty';
@override
String get whenOpenApp => 'When opening the app';
@override
String get wolTip =>
'After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.';
@override
String get write => 'Write';
@override
String get writeScriptFailTip =>
'Writing to the script failed, possibly due to lack of permissions or the directory does not exist.';
@override
String get writeScriptTip =>
'After connecting to the server, a script will be written to ~/.config/server_box to monitor the system status. You can review the script content.';
}

View File

@@ -0,0 +1,778 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'l10n.dart';
// ignore_for_file: type=lint
/// The translations for Spanish Castilian (`es`).
class AppLocalizationsEs extends AppLocalizations {
AppLocalizationsEs([String locale = 'es']) : super(locale);
@override
String get aboutThanks => 'Gracias a los siguientes participantes.';
@override
String get acceptBeta => 'Aceptar actualizaciones de la versión de prueba';
@override
String get addSystemPrivateKeyTip =>
'Actualmente no hay ninguna llave privada, ¿quieres agregar la que viene por defecto en el sistema (~/.ssh/id_rsa)?';
@override
String get added2List => 'Añadido a la lista de tareas';
@override
String get addr => 'Dirección';
@override
String get alreadyLastDir => 'Ya estás en el directorio superior';
@override
String get authFailTip =>
'La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.';
@override
String get autoBackupConflict =>
'Solo se puede activar una copia de seguridad automática a la vez';
@override
String get autoConnect => 'Conexión automática';
@override
String get autoRun => 'Ejecución automática';
@override
String get autoUpdateHomeWidget =>
'Actualizar automáticamente el widget del escritorio';
@override
String get backupTip =>
'Los datos exportados solo están encriptados de manera básica, por favor guárdalos en un lugar seguro.';
@override
String get backupVersionNotMatch =>
'La versión de la copia de seguridad no coincide, no se puede restaurar';
@override
String get battery => 'Batería';
@override
String get bgRun => 'Ejecución en segundo plano';
@override
String get bgRunTip =>
'Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.';
@override
String get closeAfterSave => 'Guardar y cerrar';
@override
String get cmd => 'Comando';
@override
String get collapseUITip =>
'¿Colapsar por defecto las listas largas en la UI?';
@override
String get conn => 'Conectar';
@override
String get container => 'Contenedor';
@override
String get containerTrySudoTip =>
'Por ejemplo: si configuras el usuario dentro de la app como aaa, pero Docker está instalado bajo el usuario root, entonces necesitarás habilitar esta opción';
@override
String get convert => 'Convertir';
@override
String get copyPath => 'Copiar ruta';
@override
String get cpuViewAsProgressTip =>
'Muestre la tasa de uso de cada CPU en estilo de barra de progreso (estilo antiguo)';
@override
String get cursorType => 'Tipo de cursor';
@override
String get customCmd => 'Comandos personalizados';
@override
String get customCmdDocUrl =>
'https://github.com/lollipopkit/flutter_server_box/wiki#custom-commands';
@override
String get customCmdHint => '\"Nombre del comando\": \"Comando\"';
@override
String get decode => 'Decodificar';
@override
String get decompress => 'Descomprimir';
@override
String get deleteServers => 'Eliminar servidores en lote';
@override
String get desktopTerminalTip =>
'Comando utilizado para abrir el emulador de terminal al iniciar sesiones SSH.';
@override
String get dirEmpty => 'Asegúrate de que el directorio esté vacío';
@override
String get disconnected => 'Desconectado';
@override
String get disk => 'Disco';
@override
String get diskHealth => 'Salud del disco';
@override
String get diskIgnorePath => 'Rutas de disco ignoradas';
@override
String get displayCpuIndex => 'Muestre el índice de CPU';
@override
String dl2Local(Object fileName) {
return '¿Descargar $fileName a local?';
}
@override
String get dockerEmptyRunningItems =>
'No hay contenedores en ejecución.\nEsto podría deberse a que:\n- El usuario con el que se instaló Docker es diferente al configurado en la app\n- La variable de entorno DOCKER_HOST no se ha leído correctamente. Puedes obtenerla ejecutando `echo \$DOCKER_HOST` en el terminal.';
@override
String dockerImagesFmt(Object count) {
return 'Total de $count imágenes';
}
@override
String get dockerNotInstalled => 'Docker no está instalado';
@override
String dockerStatusRunningAndStoppedFmt(
Object runningCount,
Object stoppedCount,
) {
return '$runningCount en ejecución, $stoppedCount detenidos';
}
@override
String dockerStatusRunningFmt(Object count) {
return '$count contenedores en ejecución';
}
@override
String get doubleColumnMode => 'Modo de doble columna';
@override
String get doubleColumnTip =>
'Esta opción solo habilita la función, si se puede activar o no depende del ancho del dispositivo';
@override
String get editVirtKeys => 'Editar teclas virtuales';
@override
String get editor => 'Editor';
@override
String get editorHighlightTip =>
'El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.';
@override
String get emulator => 'Emulador';
@override
String get encode => 'Codificar';
@override
String get envVars => 'Variable de entorno';
@override
String get experimentalFeature => 'Función experimental';
@override
String get extraArgs => 'Argumentos extra';
@override
String get fallbackSshDest => 'Destino SSH alternativo';
@override
String get fdroidReleaseTip =>
'Si descargaste esta aplicación desde F-Droid, se recomienda desactivar esta opción.';
@override
String get fgService => 'Servicio en primer plano';
@override
String get fgServiceTip =>
'Después de activarlo, algunos modelos de dispositivos pueden bloquearse. Desactivarlo puede hacer que algunos modelos no puedan mantener las conexiones SSH en segundo plano. Por favor, permita los permisos de notificación de ServerBox, la ejecución en segundo plano y el auto-despertar en la configuración del sistema.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'El archivo \'$file\' es demasiado grande \'$size\', supera el $sizeMax';
}
@override
String get followSystem => 'Seguir al sistema';
@override
String get font => 'Fuente';
@override
String get fontSize => 'Tamaño de fuente';
@override
String get force => 'Forzar';
@override
String get fullScreen => 'Modo pantalla completa';
@override
String get fullScreenJitter => 'Temblores en modo pantalla completa';
@override
String get fullScreenJitterHelp => 'Prevención de quemaduras de pantalla';
@override
String get fullScreenTip =>
'¿Debe habilitarse el modo de pantalla completa cuando el dispositivo se rote al modo horizontal? Esta opción solo se aplica a la pestaña del servidor.';
@override
String get goBackQ => '¿Regresar?';
@override
String get goto => 'Ir a';
@override
String get hideTitleBar => 'Ocultar barra de título';
@override
String get highlight => 'Resaltar código';
@override
String get homeWidgetUrlConfig => 'Configuración de URL del widget de inicio';
@override
String get host => 'Anfitrión';
@override
String httpFailedWithCode(Object code) {
return 'Fallo en la solicitud, código de estado: $code';
}
@override
String get ignoreCert => 'Ignorar certificado';
@override
String get image => 'Imagen';
@override
String get imagesList => 'Lista de imágenes';
@override
String get init => 'Inicializar';
@override
String get inner => 'Interno';
@override
String get install => 'Instalar';
@override
String get installDockerWithUrl =>
'Por favor instala Docker primero desde https://docs.docker.com/engine/install';
@override
String get invalid => 'Inválido';
@override
String get jumpServer => 'Servidor de salto';
@override
String get keepForeground => '¡Por favor, mantén la app en primer plano!';
@override
String get keepStatusWhenErr => 'Mantener el estado anterior del servidor';
@override
String get keepStatusWhenErrTip =>
'Solo aplica cuando hay errores al ejecutar scripts';
@override
String get keyAuth => 'Autenticación con llave';
@override
String get letterCache => 'Caché de letras';
@override
String get letterCacheTip =>
'Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.';
@override
String get license => 'Licencia de código abierto';
@override
String get location => 'Ubicación';
@override
String get loss => 'Tasa de pérdida';
@override
String madeWithLove(Object myGithub) {
return 'Hecho con ❤️ por $myGithub';
}
@override
String get manual => 'Manual';
@override
String get max => 'Máximo';
@override
String get maxRetryCount =>
'Número máximo de reintentos de conexión al servidor';
@override
String get maxRetryCountEqual0 => 'Reintentará infinitamente';
@override
String get min => 'Mínimo';
@override
String get mission => 'Misión';
@override
String get more => 'Más';
@override
String get moveOutServerFuncBtnsHelp =>
'Activado: se mostrará debajo de cada tarjeta en la página de servidores. Desactivado: se mostrará en la parte superior de los detalles del servidor.';
@override
String get ms => 'milisegundos';
@override
String get needHomeDir =>
'Si eres usuario de Synology, [consulta aquí](https://kb.synology.com/DSM/tutorial/user_enable_home_service). Los usuarios de otros sistemas deben buscar cómo crear un directorio home.';
@override
String get needRestart => 'Necesita reiniciar la app';
@override
String get net => 'Red';
@override
String get netViewType => 'Tipo de vista de red';
@override
String get newContainer => 'Crear contenedor nuevo';
@override
String get noLineChart => 'No utilice gráficos de líneas';
@override
String get noLineChartForCpu => 'No utilice gráficos lineales para la CPU';
@override
String get noPrivateKeyTip =>
'La clave privada no existe, puede haber sido eliminada o hay un error de configuración.';
@override
String get noPromptAgain => 'No volver a preguntar';
@override
String get node => 'Nodo';
@override
String get notAvailable => 'No disponible';
@override
String get onServerDetailPage => 'En la página de detalles del servidor';
@override
String get onlyOneLine => 'Mostrar solo en una línea (desplazable)';
@override
String get onlyWhenCoreBiggerThan8 =>
'Efectivo solo cuando el número de núcleos > 8';
@override
String get openLastPath => 'Abrir el último camino';
@override
String get openLastPathTip =>
'Los diferentes servidores tendrán diferentes registros, y lo que se registra es la ruta de salida';
@override
String get parseContainerStatsTip =>
'El análisis del estado de uso de Docker es bastante lento';
@override
String percentOfSize(Object percent, Object size) {
return 'El $percent% de $size';
}
@override
String get permission => 'Permisos';
@override
String get pingAvg => 'Promedio:';
@override
String get pingInputIP =>
'Por favor, introduce la IP de destino o el dominio';
@override
String get pingNoServer =>
'No hay servidores disponibles para hacer Ping\nPor favor, añade un servidor en la pestaña de servidores y vuelve a intentarlo';
@override
String get pkg => 'Gestión de paquetes';
@override
String get plugInType => 'Tipo de inserción';
@override
String get port => 'Puerto';
@override
String get preferDiskAmount =>
'Priorizar la visualización de la capacidad del disco';
@override
String get preview => 'Vista previa';
@override
String get privateKey => 'Llave privada';
@override
String get process => 'Proceso';
@override
String get pushToken => 'Token de notificaciones';
@override
String get pveIgnoreCertTip =>
'No se recomienda activarlo, ¡tenga cuidado con los riesgos de seguridad! Si está utilizando el certificado predeterminado de PVE, debe habilitar esta opción.';
@override
String get pveLoginFailed =>
'Fallo al iniciar sesión. No se puede autenticar con el nombre de usuario/contraseña de la configuración del servidor para el inicio de sesión de Linux PAM.';
@override
String get pveVersionLow =>
'Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.';
@override
String get pwd => 'Contraseña';
@override
String get read => 'Leer';
@override
String get reboot => 'Reiniciar';
@override
String get rememberPwdInMem => 'Recordar contraseña en la memoria';
@override
String get rememberPwdInMemTip =>
'Utilizado para contenedores, suspensión, etc.';
@override
String get rememberWindowSize => 'Recordar el tamaño de la ventana';
@override
String get remotePath => 'Ruta remota';
@override
String get restart => 'Reiniciar';
@override
String get result => 'Resultado';
@override
String get rotateAngel => 'Ángulo de rotación';
@override
String get route => 'Enrutamiento';
@override
String get run => 'Ejecutar';
@override
String get running => 'En ejecución';
@override
String get sameIdServerExist => 'Ya existe un servidor con el mismo ID';
@override
String get save => 'Guardar';
@override
String get saved => 'Guardado';
@override
String get second => 'Segundo';
@override
String get sensors => 'Sensores';
@override
String get sequence => 'Secuencia';
@override
String get server => 'Servidor';
@override
String get serverDetailOrder =>
'Orden de los componentes en la página de detalles del servidor';
@override
String get serverFuncBtns => 'Botones de función del servidor';
@override
String get serverOrder => 'Orden del servidor';
@override
String get sftpDlPrepare => 'Preparando para conectar al servidor...';
@override
String get 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`).';
@override
String get sftpRmrDirSummary =>
'Usar `rm -r` en SFTP para eliminar directorios';
@override
String get sftpSSHConnected => 'SFTP conectado...';
@override
String get sftpShowFoldersFirst => 'Mostrar carpetas primero';
@override
String get showDistLogo => 'Mostrar logo de distribución';
@override
String get shutdown => 'Apagar';
@override
String get size => 'Tamaño';
@override
String get snippet => 'Fragmento de código';
@override
String get softWrap => 'Salto de línea suave';
@override
String get specifyDev => 'Especificar dispositivo';
@override
String get specifyDevTip =>
'Por ejemplo, las estadísticas de tráfico de red son por defecto para todos los dispositivos. Aquí puede especificar un dispositivo en particular.';
@override
String get speed => 'Velocidad';
@override
String spentTime(Object time) {
return 'Tiempo gastado: $time';
}
@override
String get sshTermHelp =>
'Cuando el terminal es desplazable, arrastrar horizontalmente puede seleccionar texto. Hacer clic en el botón del teclado enciende/apaga el teclado. El icono de archivo abre el SFTP de la ruta actual. El botón del portapapeles copia el contenido cuando se selecciona texto y pega el contenido del portapapeles en el terminal cuando no se selecciona texto y hay contenido en el portapapeles. El icono de código pega fragmentos de código en el terminal y los ejecuta.';
@override
String sshTip(Object url) {
return 'Esta función está en fase de pruebas.\n\nPor favor, informa los problemas en $url, o únete a nuestro desarrollo.';
}
@override
String get sshVirtualKeyAutoOff =>
'Desactivación automática de teclas virtuales';
@override
String get start => 'Iniciar';
@override
String get stat => 'Estadísticas';
@override
String get stats => 'Estadísticas';
@override
String get stop => 'Detener';
@override
String get stopped => 'Detenido';
@override
String get storage => 'Almacenamiento';
@override
String get supportFmtArgs => 'Soporta los siguientes argumentos de formato:';
@override
String get suspend => 'Suspender';
@override
String get suspendTip =>
'La función de suspender necesita permisos de root y soporte de systemd.';
@override
String switchTo(Object val) {
return 'Cambiar a $val';
}
@override
String get sync => 'Sincronizar';
@override
String get syncTip =>
'Puede que necesites reiniciar para que algunos cambios tengan efecto.';
@override
String get system => 'Sistema';
@override
String get tag => 'Etiqueta';
@override
String get temperature => 'Temperatura';
@override
String get termFontSizeTip =>
'Este ajuste afectará el tamaño del terminal (ancho y alto). Puedes hacer zoom en la página del terminal para ajustar el tamaño de fuente de la sesión actual.';
@override
String get terminal => 'Terminal';
@override
String get test => 'Prueba';
@override
String get textScaler => 'Escalar texto';
@override
String get textScalerTip =>
'1.0 => 100% (tamaño original), solo afecta a ciertas fuentes en la página del servidor, no se recomienda modificar.';
@override
String get theme => 'Tema';
@override
String get time => 'Tiempo';
@override
String get times => 'Veces';
@override
String get total => 'Total';
@override
String get traffic => 'Tráfico';
@override
String get trySudo => 'Intentar con sudo';
@override
String get ttl => 'TTL';
@override
String get unknown => 'Desconocido';
@override
String get unkownConvertMode => 'Modo de conversión desconocido';
@override
String get update => 'Actualizar';
@override
String get updateIntervalEqual0 =>
'Si configuras esto a 0, el estado del servidor no se refrescará automáticamente.\nY no se podrá calcular el uso de CPU.';
@override
String get updateServerStatusInterval =>
'Intervalo de actualización del estado del servidor';
@override
String get upload => 'Subir';
@override
String get upsideDown => 'Invertir arriba por abajo';
@override
String get uptime => 'Tiempo de actividad';
@override
String get useCdn => 'Usando CDN';
@override
String get useCdnTip =>
'Se recomienda a los usuarios no chinos utilizar CDN. ¿Le gustaría utilizarlo?';
@override
String get useNoPwd => 'Se usará sin contraseña';
@override
String get usePodmanByDefault => 'Usar Podman por defecto';
@override
String get used => 'Usado';
@override
String get view => 'Vista';
@override
String get viewErr => 'Ver error';
@override
String get virtKeyHelpClipboard =>
'Si el terminal tiene caracteres seleccionados, entonces copiará los caracteres seleccionados al portapapeles, de lo contrario, pegará el contenido del portapapeles al terminal.';
@override
String get virtKeyHelpIME => 'Encender/apagar el teclado';
@override
String get virtKeyHelpSFTP => 'Abrir la ruta actual en SFTP.';
@override
String get waitConnection =>
'Por favor, espera a que la conexión se establezca';
@override
String get wakeLock => 'Mantener despierto';
@override
String get watchNotPaired => 'No hay un Apple Watch emparejado';
@override
String get webdavSettingEmpty => 'La configuración de Webdav está vacía';
@override
String get whenOpenApp => 'Al abrir la App';
@override
String get wolTip =>
'Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.';
@override
String get write => 'Escribir';
@override
String get writeScriptFailTip =>
'La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.';
@override
String get 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.';
}

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