Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b56e033773 |
31
.github/workflows/release.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
name: Flutter Release
|
name: Flutter Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
@@ -19,12 +18,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.32.2"
|
flutter-version: '3.24.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
|
||||||
@@ -54,28 +53,14 @@ 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 }}
|
||||||
|
|
||||||
@@ -105,9 +90,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.32.1'
|
# flutter-version: '3.22.2'
|
||||||
# - name: Build
|
# - name: Build
|
||||||
# run: dart run fl_build -p ios,mac
|
# run: dart run fl_build -p ios,mac
|
||||||
# - name: Create Release
|
# - name: Create Release
|
||||||
|
|||||||
2
.gitignore
vendored
@@ -46,7 +46,6 @@ 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
|
||||||
|
|
||||||
@@ -65,4 +64,3 @@ untranlated.json
|
|||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
more_build_data.json
|
more_build_data.json
|
||||||
trans.txt
|
trans.txt
|
||||||
android/app/.cxx
|
|
||||||
|
|||||||
31
README.md
@@ -6,17 +6,16 @@ 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="https://github.com/lollipopkit/flutter_server_box/issues/43">Linux</a> server status and tools to manage server.
|
A Flutter project which provide charts to display <a href="../../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>
|
||||||
@@ -26,26 +25,27 @@ 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.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/) |
|
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||||
| Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) |
|
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||||
|
|
||||||
Please only download pkgs from the source that **you trust**!
|
Please only download pkgs from the source that **you trust**!
|
||||||
|
|
||||||
## 🔖 Feature
|
|
||||||
|
|
||||||
- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process & Systemd`, `S.M.A.R.T`...
|
## 🔖 Feature
|
||||||
|
- `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">
|
||||||
<a href="https://qm.qq.com/q/daCGa7eShG"><img alt="qq" src="https://img.shields.io/badge/QQ-Group-pink"></a>
|
|
||||||
<a href="https://t.me/lpktg"><img alt="donate" src="https://img.shields.io/badge/Telegram-lpktg-green"></a>
|
<a href="https://t.me/lpktg"><img alt="donate" src="https://img.shields.io/badge/Telegram-lpktg-green"></a>
|
||||||
<a href="https://discord.gg/SsVNbRhK7w"><img alt="discord" src="https://img.shields.io/badge/Discord-lpkt-purple"></a>
|
<a href="https://discord.gg/SsVNbRhK7w"><img alt="discord" src="https://img.shields.io/badge/Discord-lpkt-purple"></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,33 +54,30 @@ 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`
|
||||||
|
|||||||
32
README_zh.md
@@ -6,17 +6,16 @@
|
|||||||
<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="https://github.com/lollipopkit/flutter_server_box/issues/43">Linux</a> 服务器工具箱,提供服务器状态图表和管理工具。
|
使用 Flutter 开发的 <a href="../../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>
|
||||||
|
|
||||||
## 🏙️ 截屏
|
|
||||||
|
|
||||||
|
## 🏙️ 截屏
|
||||||
<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>
|
||||||
@@ -26,19 +25,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
## 📥 安装
|
## 📥 安装
|
||||||
|
|
||||||
平台|下载
|
平台 | 下载
|
||||||
--|--
|
--- | ---
|
||||||
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.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/)
|
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||||
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)
|
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||||
|
|
||||||
请从 **信任** 的来源下载!
|
请从 **信任** 的来源下载!
|
||||||
|
|
||||||
## 🔖 特点
|
|
||||||
|
|
||||||
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程 & Systemd` 管理,`S.M.A.R.T`...
|
## 🔖 特点
|
||||||
|
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程 & Systemd` 管理...
|
||||||
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
||||||
- 本地化
|
- 本地化
|
||||||
- English, 简体中文
|
- English, 简体中文
|
||||||
@@ -46,10 +46,10 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
|||||||
- 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);
|
- 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);
|
||||||
- 感谢贡献者们!
|
- 感谢贡献者们!
|
||||||
|
|
||||||
|
|
||||||
## 🆘 帮助
|
## 🆘 帮助
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://qm.qq.com/q/daCGa7eShG"><img alt="qq" src="https://img.shields.io/badge/QQ-群-pink"></a>
|
|
||||||
<a href="https://t.me/lpktg"><img alt="donate" src="https://img.shields.io/badge/Telegram-lpktg-green"></a>
|
<a href="https://t.me/lpktg"><img alt="donate" src="https://img.shields.io/badge/Telegram-lpktg-green"></a>
|
||||||
<a href="https://discord.gg/SsVNbRhK7w"><img alt="discord" src="https://img.shields.io/badge/Discord-lpkt-purple"></a>
|
<a href="https://discord.gg/SsVNbRhK7w"><img alt="discord" src="https://img.shields.io/badge/Discord-lpkt-purple"></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,30 +58,26 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
|||||||
- **常见问题** 可以在 [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki/主页) 查看。
|
- **常见问题** 可以在 [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki/主页) 查看。
|
||||||
|
|
||||||
反馈前须知:
|
反馈前须知:
|
||||||
|
|
||||||
1. 反馈问题请附带 log(点击首页右上角),并以 bug 模版提交。
|
1. 反馈问题请附带 log(点击首页右上角),并以 bug 模版提交。
|
||||||
2. 反馈问题前请检查是否是 serverbox 的问题。
|
2. 反馈问题前请检查是否是 serverbox 的问题。
|
||||||
3. 欢迎所有有效、正面的反馈,主观(比如你觉得其他UI更好看)的反馈不一定会接受
|
3. 欢迎所有有效、正面的反馈,主观(比如你觉得其他UI更好看)的反馈不一定会接受
|
||||||
|
|
||||||
## 🧱 贡献
|
|
||||||
|
|
||||||
|
## 🧱 贡献
|
||||||
任何正面的贡献都欢迎。
|
任何正面的贡献都欢迎。
|
||||||
|
|
||||||
### 开发
|
### 开发
|
||||||
|
|
||||||
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
|
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
|
||||||
2. 克隆这个仓库, 运行 `flutter run` 启动应用
|
2. 克隆这个仓库, 运行 `flutter run` 启动应用
|
||||||
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 的 第三方全平台客户端。
|
||||||
- [更多](https://github.com/lollipopkit) - 工具 & etc.
|
- [更多](https://github.com/lollipopkit) - 工具 & etc.
|
||||||
|
|
||||||
## 📝 协议
|
|
||||||
|
|
||||||
|
## 📝 协议
|
||||||
`GPL v3 lollipopkit`
|
`GPL v3 lollipopkit`
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ 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
|
||||||
@@ -43,9 +41,8 @@ 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
|
||||||
|
|||||||
@@ -85,20 +85,13 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
// No applicationIdSuffix or resValue here
|
applicationIdSuffix '.debug'
|
||||||
}
|
}
|
||||||
|
|
||||||
profile {
|
profile {
|
||||||
// No applicationIdSuffix or resValue here
|
applicationIdSuffix '.debug'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependenciesInfo {
|
|
||||||
// Disables dependency metadata when building APKs.
|
|
||||||
includeInApk = false
|
|
||||||
// Disables dependency metadata when building Android App Bundles.
|
|
||||||
includeInBundle = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="ServerBox"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="true"
|
||||||
android:restoreAnyVersion="true"
|
android:restoreAnyVersion="true"
|
||||||
tools:targetApi="q">
|
tools:targetApi="q">
|
||||||
@@ -24,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="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|layoutDirection|fontScale|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|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
|
||||||
|
|||||||
@@ -4,158 +4,85 @@ 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 {
|
||||||
try {
|
when (intent?.action) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
"ACTION_STOP_FOREGROUND" -> {
|
||||||
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 -> {
|
||||||
if (intent == null) {
|
val notification = createNotification()
|
||||||
Log.w("ForegroundService", "onStartCommand called with null intent")
|
|
||||||
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)
|
startForeground(1, notification)
|
||||||
} catch (e: Exception) {
|
return START_STICKY
|
||||||
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,
|
||||||
"ForegroundServiceChannel",
|
chanId,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
).apply {
|
)
|
||||||
description = "For foreground service"
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
}
|
|
||||||
manager.createNotificationChannel(serviceChannel)
|
manager.createNotificationChannel(serviceChannel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotification(): Notification {
|
private fun createNotification(): Notification {
|
||||||
try {
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
this,
|
||||||
this,
|
0,
|
||||||
0,
|
notificationIntent,
|
||||||
notificationIntent,
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return 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("Running in background")
|
.setContentText("Open the app")
|
||||||
.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()
|
||||||
} catch (e: Exception) {
|
} else {
|
||||||
logError("Error creating notification", e)
|
Notification.Builder(this)
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopForegroundService() {
|
fun stopForegroundService() {
|
||||||
try {
|
stopForeground(true)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,15 +9,13 @@ 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/main_chan").apply {
|
MethodChannel(binaryMessenger, "tech.lolli.toolbox/app_retain").apply {
|
||||||
setMethodCallHandler { method, result ->
|
setMethodCallHandler { method, result ->
|
||||||
when (method.method) {
|
when (method.method) {
|
||||||
"sendToBackground" -> {
|
"sendToBackground" -> {
|
||||||
@@ -25,19 +23,12 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
"startService" -> {
|
"startService" -> {
|
||||||
try {
|
reqPerm()
|
||||||
reqPerm()
|
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
||||||
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
startForegroundService(serviceIntent)
|
||||||
startForegroundService(serviceIntent)
|
} else {
|
||||||
} else {
|
startService(serviceIntent)
|
||||||
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" -> {
|
||||||
@@ -45,12 +36,6 @@ 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()
|
||||||
}
|
}
|
||||||
@@ -61,21 +46,13 @@ 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) {
|
||||||
try {
|
ActivityCompat.requestPermissions(
|
||||||
ActivityCompat.requestPermissions(
|
this,
|
||||||
this,
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
123,
|
||||||
123,
|
)
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Log error but don't crash
|
|
||||||
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ 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.CoroutineScope
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
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) {
|
||||||
@@ -26,6 +23,7 @@ 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)
|
||||||
@@ -38,10 +36,6 @@ 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)
|
||||||
@@ -60,13 +54,11 @@ class HomeWidget : AppWidgetProvider() {
|
|||||||
views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate)
|
views.setOnClickPendingIntent(R.id.widget_container, pendingUpdate)
|
||||||
|
|
||||||
if (url.isNullOrEmpty()) {
|
if (url.isNullOrEmpty()) {
|
||||||
views.setTextViewText(R.id.widget_name, "No URL")
|
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE)
|
||||||
// Update the widget to display a message for missing URL
|
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE)
|
||||||
views.setViewVisibility(R.id.error_message, View.VISIBLE)
|
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE)
|
||||||
views.setTextViewText(R.id.error_message, "Please configure the widget URL.")
|
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE)
|
||||||
views.setViewVisibility(R.id.widget_content, View.GONE)
|
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId")
|
||||||
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 {
|
||||||
@@ -76,53 +68,44 @@ class HomeWidget : AppWidgetProvider() {
|
|||||||
views.setViewVisibility(R.id.widget_net_label, View.VISIBLE)
|
views.setViewVisibility(R.id.widget_net_label, View.VISIBLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
val jsonStr = URL(url).readText()
|
||||||
connection.requestMethod = "GET"
|
val jsonObject = JSONObject(jsonStr)
|
||||||
val responseCode = connection.responseCode
|
val data = jsonObject.getJSONObject("data")
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
val server = data.getString("name")
|
||||||
val jsonStr = connection.inputStream.bufferedReader().use { it.readText() }
|
val cpu = data.getString("cpu")
|
||||||
val jsonObject = JSONObject(jsonStr)
|
val mem = data.getString("mem")
|
||||||
val data = jsonObject.getJSONObject("data")
|
val disk = data.getString("disk")
|
||||||
val server = data.getString("name")
|
val net = data.getString("net")
|
||||||
val cpu = data.getString("cpu")
|
|
||||||
val mem = data.getString("mem")
|
GlobalScope.launch(Dispatchers.Main) main@ {
|
||||||
val disk = data.getString("disk")
|
// mem or disk is empty -> get status failed
|
||||||
val net = data.getString("net")
|
// (cpu | net) isEmpty -> data is not ready
|
||||||
withContext(Dispatchers.Main) {
|
if (mem.isEmpty() || disk.isEmpty()) {
|
||||||
if (mem.isEmpty() || disk.isEmpty()) {
|
return@main
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
} else {
|
views.setTextViewText(R.id.widget_name, server)
|
||||||
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) {
|
||||||
Log.e("HomeWidget", "Error updating widget: ${e.localizedMessage}", e)
|
println("ServerBoxHomeWidget: ${e.localizedMessage}")
|
||||||
withContext(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) main@ {
|
||||||
views.setTextViewText(R.id.widget_name, "Error")
|
views.setViewVisibility(R.id.widget_cpu_label, View.INVISIBLE)
|
||||||
// Update the widget to display a message for data retrieval failure
|
views.setViewVisibility(R.id.widget_mem_label, View.INVISIBLE)
|
||||||
views.setViewVisibility(R.id.error_message, View.VISIBLE)
|
views.setViewVisibility(R.id.widget_disk_label, View.INVISIBLE)
|
||||||
views.setTextViewText(R.id.error_message, "Failed to retrieve data.")
|
views.setViewVisibility(R.id.widget_net_label, View.INVISIBLE)
|
||||||
views.setViewVisibility(R.id.widget_content, View.GONE)
|
views.setTextViewText(R.id.widget_name, "ID: $appWidgetId")
|
||||||
views.setFloat(R.id.widget_name, "setAlpha", 1f)
|
views.setTextViewText(R.id.widget_mem, e.localizedMessage)
|
||||||
views.setFloat(R.id.error_message, "setAlpha", 1f)
|
|
||||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,151 +16,124 @@
|
|||||||
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" />
|
||||||
|
|
||||||
<!-- Wrap the content in a LinearLayout for easy visibility management -->
|
<RelativeLayout
|
||||||
<LinearLayout
|
android:id="@+id/widget_container_inner"
|
||||||
android:id="@+id/widget_content"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:gravity="center_vertical"
|
||||||
android:layout_below="@id/widget_name"
|
|
||||||
android:paddingTop="13dp">
|
android:paddingTop="13dp">
|
||||||
|
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:id="@+id/widget_container_inner"
|
android:id="@+id/widget_cpu_label"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="2.7dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingTop="13dp"
|
android:orientation="horizontal">
|
||||||
android:animateLayoutChanges="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ImageView
|
||||||
android:id="@+id/widget_cpu_label"
|
android:layout_width="17dp"
|
||||||
android:layout_width="wrap_content"
|
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_height="wrap_content"
|
||||||
android:paddingBottom="2.7dp"
|
android:layout_marginStart="11dp"
|
||||||
android:gravity="center_vertical"
|
android:singleLine="true"
|
||||||
android:orientation="horizontal">
|
android:ellipsize = "marquee"
|
||||||
|
android:textColor="@color/widgetSummaryText"
|
||||||
<ImageView
|
android:textSize="12.7sp"
|
||||||
android:layout_width="17dp"
|
tools:text="CPU" />
|
||||||
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:paddingBottom="2.7dp"
|
android:layout_marginStart="11dp"
|
||||||
android:layout_below="@id/widget_cpu_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/memory_24">
|
|
||||||
</ImageView>
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/widget_mem"
|
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="Mem" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<ImageView
|
||||||
|
android:layout_width="17dp"
|
||||||
|
android:layout_height="17dp"
|
||||||
|
android:src="@drawable/storage_24">
|
||||||
|
</ImageView>
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/widget_disk_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: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="Disk" />
|
||||||
|
|
||||||
<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_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="Disk" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<ImageView
|
||||||
|
android:layout_width="17dp"
|
||||||
|
android:layout_height="17dp"
|
||||||
|
android:src="@drawable/net_24">
|
||||||
|
</ImageView>
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/widget_net_label"
|
android:id="@+id/widget_net"
|
||||||
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="Net" />
|
||||||
|
|
||||||
<ImageView
|
</LinearLayout>
|
||||||
android:layout_width="17dp"
|
|
||||||
android:layout_height="17dp"
|
|
||||||
android:src="@drawable/net_24">
|
|
||||||
</ImageView>
|
|
||||||
|
|
||||||
<TextView
|
</RelativeLayout>
|
||||||
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"
|
||||||
@@ -170,8 +143,6 @@
|
|||||||
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>
|
||||||
|
Before Width: | Height: | Size: 761 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 895 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">ServerBox</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<full-backup-content>
|
|
||||||
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
|
|
||||||
</full-backup-content>
|
|
||||||
@@ -9,23 +9,6 @@ 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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
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
|
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ 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-8.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||||
|
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
|
||||||
|
|||||||
@@ -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 '8.6.0' apply false
|
id "com.android.application" version "7.4.2" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
|
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -6,9 +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 (2.4.3):
|
- flutter_native_splash (0.0.1):
|
||||||
- Flutter
|
|
||||||
- flutter_secure_storage (6.0.0):
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- icloud_storage (0.0.1):
|
- icloud_storage (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -33,6 +31,8 @@ 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`)
|
||||||
@@ -40,7 +40,6 @@ DEPENDENCIES:
|
|||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
|
||||||
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
|
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
@@ -51,6 +50,7 @@ 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:
|
||||||
@@ -63,8 +63,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
|
||||||
icloud_storage:
|
icloud_storage:
|
||||||
:path: ".symlinks/plugins/icloud_storage/ios"
|
:path: ".symlinks/plugins/icloud_storage/ios"
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
@@ -85,25 +83,27 @@ 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: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
|
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4
|
||||||
file_picker: fb04e739ae6239a76ce1f571863a196a922c87d4
|
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
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.16.2
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
@@ -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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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 = 1201;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
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.1201;
|
MARKETING_VERSION = 1.0.1104;
|
||||||
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;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
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
|
||||||
@@ -44,13 +43,11 @@
|
|||||||
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">
|
||||||
|
|||||||
@@ -14,18 +14,11 @@ class PhoneConnMgr: NSObject, WCSessionDelegate, ObservableObject {
|
|||||||
set {
|
set {
|
||||||
Store.setCtx(newValue)
|
Store.setCtx(newValue)
|
||||||
updateUrls(newValue)
|
updateUrls(newValue)
|
||||||
|
|
||||||
// Notify the view to update, but the [urls] are already published
|
|
||||||
// so the view will automatically update when [urls] changes.
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// self.objectWillChange.send()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
get {
|
get {
|
||||||
return _ctx
|
return _ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var userInfo: [String: Any] = [:]
|
|
||||||
@Published var urls: [String] = []
|
@Published var urls: [String] = []
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
@@ -37,84 +30,23 @@ class PhoneConnMgr: NSObject, WCSessionDelegate, ObservableObject {
|
|||||||
session?.delegate = self
|
session?.delegate = self
|
||||||
session?.activate()
|
session?.activate()
|
||||||
|
|
||||||
_ctx = Store.getCtx()
|
ctx = Store.getCtx()
|
||||||
updateUrls(_ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUrls(_ val: [String: Any]) {
|
func updateUrls(_ val: [String: Any]) {
|
||||||
if let urls = val["urls"] as? [String] {
|
if let urls = val["urls"] as? [String] {
|
||||||
DispatchQueue.main.async {
|
self.urls = urls.filter { !$0.isEmpty }
|
||||||
self.urls = urls.filter { !$0.isEmpty }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func session(
|
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||||
_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState,
|
|
||||||
error: Error?
|
|
||||||
) {
|
|
||||||
// Request latest data when the session is activated
|
|
||||||
if activationState == .activated {
|
|
||||||
requestLatestData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive realtime msgs
|
// implement session:didReceiveApplicationContext:
|
||||||
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
|
||||||
DispatchQueue.main.async {
|
ctx = applicationContext
|
||||||
self.ctx = message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive UserInfo
|
|
||||||
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.ctx = userInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive Application Context
|
|
||||||
func session(
|
|
||||||
_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]
|
|
||||||
) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.ctx = applicationContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestLatestData(timeout: TimeInterval = 5.0, maxRetries: Int = 1) {
|
|
||||||
guard let session = session, session.isReachable else { return }
|
|
||||||
|
|
||||||
var didReceiveResponse = false
|
|
||||||
var retries = 0
|
|
||||||
|
|
||||||
func sendRequest() {
|
|
||||||
session.sendMessage(["action": "requestData"]) { response in
|
|
||||||
didReceiveResponse = true
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.ctx = response
|
|
||||||
}
|
|
||||||
} errorHandler: { error in
|
|
||||||
print("Request data failed: \(error)")
|
|
||||||
// Optionally, handle error UI here
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout handling
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
if !didReceiveResponse {
|
|
||||||
if retries < maxRetries {
|
|
||||||
retries += 1
|
|
||||||
print("No response, retrying requestLatestData (\(retries))...")
|
|
||||||
sendRequest()
|
|
||||||
} else {
|
|
||||||
print("Request data timed out after \(retries + 1) attempts.")
|
|
||||||
// Optionally, update UI to indicate timeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendRequest()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
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
|
||||||
141
lib/app.dart
@@ -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/generated/l10n/lib_l10n.dart';
|
import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.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/generated/l10n/l10n.dart';
|
import 'package:server_box/view/page/home/home.dart';
|
||||||
import 'package:server_box/view/page/home.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
|
|
||||||
part 'intro.dart';
|
part 'intro.dart';
|
||||||
|
|
||||||
@@ -22,67 +22,48 @@ class MyApp extends StatelessWidget {
|
|||||||
listenable: RNodes.app,
|
listenable: RNodes.app,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
||||||
return _build(context);
|
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
||||||
|
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(
|
||||||
return _buildDynamicColor(context);
|
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 _build(BuildContext context) {
|
Widget _buildApp(BuildContext ctx,
|
||||||
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
{required ThemeData light, required ThemeData dark}) {
|
||||||
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) {
|
||||||
@@ -94,14 +75,6 @@ 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,
|
||||||
@@ -114,25 +87,21 @@ 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: Builder(
|
home: VirtualWindowFrame(
|
||||||
builder: (context) {
|
child: Builder(
|
||||||
context.setLibL10n();
|
builder: (context) {
|
||||||
final appL10n = AppLocalizations.of(context);
|
context.setLibL10n();
|
||||||
if (appL10n != null) l10n = appL10n;
|
final appL10n = AppLocalizations.of(context);
|
||||||
|
if (appL10n != null) l10n = appL10n;
|
||||||
|
|
||||||
Widget child;
|
final intros = _IntroPage.builders;
|
||||||
final intros = _IntroPage.builders;
|
if (intros.isNotEmpty) {
|
||||||
if (intros.isNotEmpty) {
|
return _IntroPage(intros);
|
||||||
child = _IntroPage(intros);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
child = const HomePage();
|
return const HomePage();
|
||||||
|
},
|
||||||
return VirtualWindowFrame(
|
),
|
||||||
title: BuildData.name,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
lib/core/channel/bg_run.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
12
lib/core/channel/home_widget.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:server_box/generated/l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n_en.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)!;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ extension SftpFileX on SftpFileMode {
|
|||||||
|
|
||||||
UnixPerm toUnixPerm() {
|
UnixPerm toUnixPerm() {
|
||||||
return UnixPerm(
|
return UnixPerm(
|
||||||
user: UnixPermOp(
|
user: RWX(
|
||||||
r: userRead,
|
r: userRead,
|
||||||
w: userWrite,
|
w: userWrite,
|
||||||
x: userExecute,
|
x: userExecute,
|
||||||
),
|
),
|
||||||
group: UnixPermOp(
|
group: RWX(
|
||||||
r: groupRead,
|
r: groupRead,
|
||||||
w: groupWrite,
|
w: groupWrite,
|
||||||
x: groupExecute,
|
x: groupExecute,
|
||||||
),
|
),
|
||||||
other: UnixPermOp(
|
other: RWX(
|
||||||
r: otherRead,
|
r: otherRead,
|
||||||
w: otherWrite,
|
w: otherWrite,
|
||||||
x: otherExecute,
|
x: otherExecute,
|
||||||
|
|||||||
@@ -1,9 +1,169 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
/// The args class for [AppRoute].
|
class AppRoutes {
|
||||||
final class SpiRequiredArgs {
|
final Widget page;
|
||||||
/// The only required argument for this class.
|
final String title;
|
||||||
final Spi spi;
|
|
||||||
|
|
||||||
const SpiRequiredArgs(this.spi);
|
AppRoutes(this.page, this.title);
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,39 @@
|
|||||||
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/bak/backup2.dart';
|
import 'package:server_box/data/model/app/backup.dart';
|
||||||
import 'package:server_box/data/model/app/bak/utils.dart';
|
import 'package:server_box/data/store/no_backup.dart';
|
||||||
|
|
||||||
const bakSync = BakSyncer._();
|
const bakSync = BakSyncer._();
|
||||||
|
|
||||||
final icloud = ICloud(containerId: 'iCloud.tech.lolli.serverbox');
|
final class BakSyncer extends SyncIface<Backup> {
|
||||||
|
|
||||||
final class BakSyncer extends SyncIface {
|
|
||||||
const BakSyncer._() : super();
|
const BakSyncer._() : super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> saveToFile() => BackupV2.backup();
|
Future<void> saveToFile() => Backup.backup();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Mergeable> fromFile(String path) async {
|
Future<Backup> fromFile(String path) async {
|
||||||
final content = await File(path).readAsString();
|
final content = await File(path).readAsString();
|
||||||
return MergeableUtils.fromJsonString(content).$1;
|
return Backup.fromJsonString(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RemoteStorage? get remoteStorage {
|
Future<RemoteStorage?> get remoteStorage async {
|
||||||
final icloudEnabled = PrefProps.icloudSync.get();
|
if (isMacOS || isIOS) await icloud.init('iCloud.tech.lolli.serverbox');
|
||||||
|
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 = PrefProps.webdavSync.get();
|
final webdavEnabled = settings.webdavSync.fetch();
|
||||||
if (webdavEnabled) return Webdav.shared;
|
if (webdavEnabled) return webdav;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ 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/model/server/server_private_info.dart';
|
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
|
|
||||||
/// Must put this func out of any Class.
|
/// Must put this func out of any Class.
|
||||||
///
|
///
|
||||||
/// Because of this function is called by [compute].
|
/// Because of this function is called by [compute].
|
||||||
@@ -31,7 +32,7 @@ enum GenSSHClientStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getPrivateKey(String id) {
|
String getPrivateKey(String id) {
|
||||||
final pki = Stores.key.fetchOne(id);
|
final pki = Stores.key.get(id);
|
||||||
if (pki == null) {
|
if (pki == null) {
|
||||||
throw SSHErr(
|
throw SSHErr(
|
||||||
type: SSHErrType.noPrivateKey,
|
type: SSHErrType.noPrivateKey,
|
||||||
@@ -58,7 +59,7 @@ Future<SSHClient> genClient(
|
|||||||
Spi? jumpSpi,
|
Spi? jumpSpi,
|
||||||
|
|
||||||
/// Handle keyboard-interactive authentication
|
/// Handle keyboard-interactive authentication
|
||||||
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
FutureOr<List<String>?> Function(SSHUserInfoRequest)? onKeyboardInteractive,
|
||||||
}) async {
|
}) async {
|
||||||
onStatus?.call(GenSSHClientStatus.socket);
|
onStatus?.call(GenSSHClientStatus.socket);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:server_box/core/extension/context/locale.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/provider/app.dart';
|
import 'package:server_box/data/provider/app.dart';
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ abstract final class KeybordInteractive {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog(
|
final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog(
|
||||||
title: libL10n.pwd,
|
title: l10n.pwd,
|
||||||
id: spi.id,
|
id: spi.id,
|
||||||
label: spi.id,
|
label: spi.id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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';
|
||||||
@@ -45,24 +46,19 @@ class Backup implements Mergeable {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() => _$BackupToJson(this);
|
Map<String, dynamic> toJson() => _$BackupToJson(this);
|
||||||
|
|
||||||
static Future<Backup> loadFromStore() async {
|
Backup.loadFromStore()
|
||||||
final lastModTime = Stores.lastModTime;
|
: version = backupFormatVersion,
|
||||||
return Backup(
|
date = DateTime.now().toString().split('.').firstOrNull ?? '',
|
||||||
version: backupFormatVersion,
|
spis = Stores.server.fetch(),
|
||||||
date: DateTime.now().toString().split('.').firstOrNull ?? '',
|
snippets = Stores.snippet.fetch(),
|
||||||
spis: Stores.server.fetch(),
|
keys = Stores.key.fetch(),
|
||||||
snippets: Stores.snippet.fetch(),
|
container = Stores.container.box.toJson(),
|
||||||
keys: Stores.key.fetch(),
|
lastModTime = Stores.lastModTime,
|
||||||
container: Stores.container.getAllMap(),
|
history = Stores.history.box.toJson(),
|
||||||
lastModTime: lastModTime,
|
settings = Stores.setting.box.toJson();
|
||||||
history: Stores.history.getAllMap(),
|
|
||||||
settings: Stores.setting.getAllMap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<String> backup([String? name]) async {
|
static Future<String> backup([String? name]) async {
|
||||||
final bak = await Backup.loadFromStore();
|
final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson()));
|
||||||
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;
|
||||||
@@ -70,7 +66,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;
|
final curTime = Stores.lastModTime ?? 0;
|
||||||
final bakTime = lastModTime ?? 0;
|
final bakTime = lastModTime ?? 0;
|
||||||
final shouldRestore = force || curTime < bakTime;
|
final shouldRestore = force || curTime < bakTime;
|
||||||
if (!shouldRestore) {
|
if (!shouldRestore) {
|
||||||
@@ -234,4 +230,3 @@ String _diyDecrypt(String raw) {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
37
lib/data/model/app/backup.g.dart
Normal 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,
|
||||||
|
};
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
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, String? password]) async {
|
|
||||||
final bak = await BackupV2.loadFromStore();
|
|
||||||
var result = json.encode(bak.toJson());
|
|
||||||
|
|
||||||
if (password != null && password.isNotEmpty) {
|
|
||||||
result = Cryptor.encrypt(result, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
final path = Paths.doc.joinPath(name ?? Miscs.bakFileName);
|
|
||||||
await File(path).writeAsString(result);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory BackupV2.fromJsonString(String jsonString, [String? password]) {
|
|
||||||
if (Cryptor.isEncrypted(jsonString)) {
|
|
||||||
if (password == null || password.isEmpty) {
|
|
||||||
throw Exception('Backup is encrypted but no password provided');
|
|
||||||
}
|
|
||||||
jsonString = Cryptor.decrypt(jsonString, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
final map = json.decode(jsonString) as Map<String, dynamic>;
|
|
||||||
return BackupV2.fromJson(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// 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
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$BackupV2 {
|
|
||||||
|
|
||||||
int get version; int get date; Map<String, Object?> get spis; Map<String, Object?> get snippets; Map<String, Object?> get keys; Map<String, Object?> get container; Map<String, Object?> get history; Map<String, Object?> get settings;
|
|
||||||
/// Create a copy of BackupV2
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$BackupV2CopyWith<BackupV2> get copyWith => _$BackupV2CopyWithImpl<BackupV2>(this as BackupV2, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this BackupV2 to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BackupV2&&(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));
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BackupV2(version: $version, date: $date, spis: $spis, snippets: $snippets, keys: $keys, container: $container, history: $history, settings: $settings)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $BackupV2CopyWith<$Res> {
|
|
||||||
factory $BackupV2CopyWith(BackupV2 value, $Res Function(BackupV2) _then) = _$BackupV2CopyWithImpl;
|
|
||||||
@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>
|
|
||||||
implements $BackupV2CopyWith<$Res> {
|
|
||||||
_$BackupV2CopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final BackupV2 _self;
|
|
||||||
final $Res Function(BackupV2) _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(_self.copyWith(
|
|
||||||
version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,spis: null == spis ? _self.spis : spis // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,snippets: null == snippets ? _self.snippets : snippets // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,keys: null == keys ? _self.keys : keys // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,container: null == container ? _self.container : container // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,history: null == history ? _self.history : history // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,settings: null == settings ? _self.settings : settings // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _BackupV2 extends BackupV2 {
|
|
||||||
const _BackupV2({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 _BackupV2.fromJson(Map<String, dynamic> json) => _$BackupV2FromJson(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of BackupV2
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$BackupV2CopyWith<_BackupV2> get copyWith => __$BackupV2CopyWithImpl<_BackupV2>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$BackupV2ToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackupV2&&(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));
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BackupV2(version: $version, date: $date, spis: $spis, snippets: $snippets, keys: $keys, container: $container, history: $history, settings: $settings)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$BackupV2CopyWith<$Res> implements $BackupV2CopyWith<$Res> {
|
|
||||||
factory _$BackupV2CopyWith(_BackupV2 value, $Res Function(_BackupV2) _then) = __$BackupV2CopyWithImpl;
|
|
||||||
@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 __$BackupV2CopyWithImpl<$Res>
|
|
||||||
implements _$BackupV2CopyWith<$Res> {
|
|
||||||
__$BackupV2CopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _BackupV2 _self;
|
|
||||||
final $Res Function(_BackupV2) _then;
|
|
||||||
|
|
||||||
/// Create a copy of BackupV2
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $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(_BackupV2(
|
|
||||||
version: null == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,spis: null == spis ? _self._spis : spis // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,snippets: null == snippets ? _self._snippets : snippets // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,keys: null == keys ? _self._keys : keys // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,container: null == container ? _self._container : container // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,history: null == history ? _self._history : history // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,settings: null == settings ? _self._settings : settings // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, Object?>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'backup2.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_BackupV2 _$BackupV2FromJson(Map<String, dynamic> json) => _BackupV2(
|
|
||||||
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> _$BackupV2ToJson(_BackupV2 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,
|
|
||||||
};
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
import 'package:computer/computer.dart';
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
|
||||||
import 'package:server_box/data/model/app/bak/backup2.dart';
|
|
||||||
import 'package:server_box/data/model/app/bak/backup_source.dart';
|
|
||||||
import 'package:server_box/data/model/app/bak/utils.dart';
|
|
||||||
import 'package:server_box/data/res/store.dart';
|
|
||||||
|
|
||||||
/// Service class for handling backup operations
|
|
||||||
class BackupService {
|
|
||||||
/// Perform backup operation with the given source
|
|
||||||
static Future<void> backup(BuildContext context, BackupSource source) async {
|
|
||||||
final password = await _getBackupPassword(context);
|
|
||||||
if (password == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final path = await BackupV2.backup(null, password.isEmpty ? null : password);
|
|
||||||
await source.saveContent(path);
|
|
||||||
|
|
||||||
// Show success message for clipboard source
|
|
||||||
if (source is ClipboardBackupSource) {
|
|
||||||
context.showSnackBar(libL10n.success);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
context.showErrDialog(e, s, libL10n.backup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Perform restore operation with the given source
|
|
||||||
static Future<void> restore(BuildContext context, BackupSource source) async {
|
|
||||||
final text = await source.getContent();
|
|
||||||
if (text == null) {
|
|
||||||
// Show empty message for clipboard source
|
|
||||||
if (source is ClipboardBackupSource) {
|
|
||||||
context.showSnackBar(libL10n.empty);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await restoreFromText(context, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle password dialog for backup operations
|
|
||||||
static Future<String?> _getBackupPassword(BuildContext context) async {
|
|
||||||
final savedPassword = await Stores.setting.backupasswd.read();
|
|
||||||
String? password;
|
|
||||||
|
|
||||||
if (savedPassword != null && savedPassword.isNotEmpty) {
|
|
||||||
// Use saved password or ask for custom password
|
|
||||||
final useCustom = await context.showRoundDialog<bool>(
|
|
||||||
title: l10n.backupPassword,
|
|
||||||
child: Text(l10n.backupPasswordTip),
|
|
||||||
actions: [
|
|
||||||
Btn.cancel(),
|
|
||||||
TextButton(onPressed: () => context.pop(false), child: Text(l10n.backupPasswordSet)),
|
|
||||||
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.custom)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (useCustom == null) return null;
|
|
||||||
|
|
||||||
if (useCustom) {
|
|
||||||
password = await _showPasswordDialog(context, initial: savedPassword);
|
|
||||||
} else {
|
|
||||||
password = savedPassword;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No saved password, ask if user wants to set one
|
|
||||||
password = await _showPasswordDialog(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle restore from text with decryption support
|
|
||||||
static Future<void> restoreFromText(BuildContext context, String text) async {
|
|
||||||
// Check if backup is encrypted
|
|
||||||
final isEncrypted = Cryptor.isEncrypted(text);
|
|
||||||
String? password;
|
|
||||||
|
|
||||||
if (!isEncrypted) {
|
|
||||||
try {
|
|
||||||
final (backup, err) = await context.showLoadingDialog(
|
|
||||||
fn: () => Computer.shared.start(MergeableUtils.fromJsonString, text),
|
|
||||||
);
|
|
||||||
if (err != null || backup == null) return;
|
|
||||||
|
|
||||||
await _confirmAndRestore(context, backup);
|
|
||||||
} catch (e, s) {
|
|
||||||
Loggers.app.warning('Import backup failed', e, s);
|
|
||||||
context.showErrDialog(e, s, libL10n.restore);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try with saved password first
|
|
||||||
final savedPassword = await Stores.setting.backupasswd.read();
|
|
||||||
if (savedPassword != null && savedPassword.isNotEmpty) {
|
|
||||||
try {
|
|
||||||
final (backup, err) = await context.showLoadingDialog(
|
|
||||||
fn: () => Computer.shared.start((args) => MergeableUtils.fromJsonString(args.$1, args.$2), (
|
|
||||||
text,
|
|
||||||
savedPassword,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
if (err == null && backup != null) {
|
|
||||||
await _confirmAndRestore(context, backup);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Saved password failed, will prompt for manual input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt for password with retry logic
|
|
||||||
while (true) {
|
|
||||||
password = await _showPasswordDialog(context, title: libL10n.pwd, hint: l10n.backupEncrypted);
|
|
||||||
if (password == null) return; // User cancelled
|
|
||||||
|
|
||||||
try {
|
|
||||||
final (backup, err) = await context.showLoadingDialog(
|
|
||||||
fn: () => Computer.shared.start((args) => MergeableUtils.fromJsonString(args.$1, args.$2), (
|
|
||||||
text,
|
|
||||||
password,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
if (err != null || backup == null) continue;
|
|
||||||
|
|
||||||
await _confirmAndRestore(context, backup);
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
if (e.toString().contains('incorrect password') || e.toString().contains('Failed to decrypt')) {
|
|
||||||
final retry = await context.showRoundDialog<bool>(
|
|
||||||
title: l10n.backupPasswordWrong,
|
|
||||||
child: Text(l10n.backupPasswordWrong),
|
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => context.pop(false), child: Text(libL10n.cancel)),
|
|
||||||
TextButton(onPressed: () => context.pop(true), child: Text(libL10n.retry)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (retry != true) return;
|
|
||||||
continue; // Try again
|
|
||||||
} else {
|
|
||||||
// Other error, show and exit
|
|
||||||
context.showErrDialog(e, null, libL10n.restore);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Confirm and execute restore operation
|
|
||||||
static Future<void> _confirmAndRestore(BuildContext context, (dynamic, String) backup) async {
|
|
||||||
await context.showRoundDialog(
|
|
||||||
title: libL10n.restore,
|
|
||||||
child: Text(libL10n.askContinue('${libL10n.restore} ${libL10n.backup}(${backup.$2})')),
|
|
||||||
actions: Btn.ok(
|
|
||||||
onTap: () async {
|
|
||||||
await backup.$1.merge(force: true);
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
).toList,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show password input dialog
|
|
||||||
static Future<String?> _showPasswordDialog(
|
|
||||||
BuildContext context, {
|
|
||||||
String? initial,
|
|
||||||
String? title,
|
|
||||||
String? hint,
|
|
||||||
}) async {
|
|
||||||
final controller = TextEditingController(text: initial ?? '');
|
|
||||||
final result = await context.showRoundDialog<String>(
|
|
||||||
title: title ?? libL10n.pwd,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(hint ?? l10n.backupPasswordTip, style: UIs.textGrey),
|
|
||||||
UIs.height13,
|
|
||||||
Input(
|
|
||||||
label: l10n.backupPassword,
|
|
||||||
controller: controller,
|
|
||||||
obscureText: true,
|
|
||||||
onSubmitted: (_) => context.pop(controller.text),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
Btn.cancel(),
|
|
||||||
TextButton(onPressed: () => context.pop(controller.text), child: Text(libL10n.ok)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
controller.dispose();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Abstract interface for backup content sources
|
|
||||||
abstract class BackupSource {
|
|
||||||
/// Get content from this source for restore
|
|
||||||
Future<String?> getContent();
|
|
||||||
|
|
||||||
/// Save content to this source for backup
|
|
||||||
Future<void> saveContent(String filePath);
|
|
||||||
|
|
||||||
/// Display name for this source
|
|
||||||
String get displayName;
|
|
||||||
|
|
||||||
/// Icon for this source
|
|
||||||
IconData get icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// File-based backup source
|
|
||||||
class FileBackupSource implements BackupSource {
|
|
||||||
@override
|
|
||||||
Future<String?> getContent() async {
|
|
||||||
return await Pfs.pickFileString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> saveContent(String filePath) async {
|
|
||||||
await Pfs.sharePaths(paths: [filePath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get displayName => libL10n.file;
|
|
||||||
|
|
||||||
@override
|
|
||||||
IconData get icon => Icons.file_open;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clipboard-based backup source
|
|
||||||
class ClipboardBackupSource implements BackupSource {
|
|
||||||
@override
|
|
||||||
Future<String?> getContent() async {
|
|
||||||
final text = await Pfs.paste();
|
|
||||||
if (text == null || text.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return text.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> saveContent(String filePath) async {
|
|
||||||
final content = await File(filePath).readAsString();
|
|
||||||
Pfs.copy(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get displayName => libL10n.clipboard;
|
|
||||||
|
|
||||||
@override
|
|
||||||
IconData get icon => Icons.content_paste;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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, [String? password]) {
|
|
||||||
try {
|
|
||||||
final bak = BackupV2.fromJsonString(json, password);
|
|
||||||
return (bak, DateTime.fromMillisecondsSinceEpoch(bak.date).hms());
|
|
||||||
} catch (e) {
|
|
||||||
final bak = Backup.fromJsonString(json);
|
|
||||||
return (bak, bak.date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,27 @@
|
|||||||
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,
|
||||||
@@ -14,7 +35,7 @@ enum SSHErrType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SSHErr extends Err<SSHErrType> {
|
class SSHErr extends Err<SSHErrType> {
|
||||||
SSHErr({required super.type, super.message});
|
SSHErr({required super.type, super.message}) : super(from: ErrFrom.ssh);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get solution => switch (type) {
|
String? get solution => switch (type) {
|
||||||
@@ -24,6 +45,11 @@ 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 {
|
||||||
@@ -39,10 +65,16 @@ 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 {
|
||||||
@@ -52,10 +84,15 @@ enum ICloudErrType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ICloudErr extends Err<ICloudErrType> {
|
class ICloudErr extends Err<ICloudErrType> {
|
||||||
ICloudErr({required super.type, super.message});
|
ICloudErr({required super.type, super.message}) : super(from: ErrFrom.icloud);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get solution => null;
|
String? get solution => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ICloudErr<$type>: $message';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WebdavErrType {
|
enum WebdavErrType {
|
||||||
@@ -65,10 +102,15 @@ enum WebdavErrType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class WebdavErr extends Err<WebdavErrType> {
|
class WebdavErr extends Err<WebdavErrType> {
|
||||||
WebdavErr({required super.type, super.message});
|
WebdavErr({required super.type, super.message}) : super(from: ErrFrom.webdav);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get solution => null;
|
String? get solution => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebdavErr<$type>: $message';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PveErrType {
|
enum PveErrType {
|
||||||
@@ -79,8 +121,13 @@ enum PveErrType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PveErr extends Err<PveErrType> {
|
class PveErr extends Err<PveErrType> {
|
||||||
PveErr({required super.type, super.message});
|
PveErr({required super.type, super.message}) : super(from: ErrFrom.status);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get solution => null;
|
String? get solution => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PveErr<$type>: $message';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,36 @@
|
|||||||
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 {
|
||||||
terminal(),
|
@HiveField(0)
|
||||||
sftp(),
|
terminal._(),
|
||||||
container(),
|
@HiveField(1)
|
||||||
process(),
|
sftp._(),
|
||||||
//pkg(),
|
@HiveField(2)
|
||||||
snippet(),
|
container._(),
|
||||||
iperf(),
|
@HiveField(3)
|
||||||
// pve(),
|
process._(),
|
||||||
systemd(1058),
|
//@HiveField(4)
|
||||||
|
//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!) {
|
||||||
|
|||||||
71
lib/data/model/app/menu/server_func.g.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
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) {
|
||||||
|
|||||||
51
lib/data/model/app/net_view.g.dart
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -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,20 +31,19 @@ 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,
|
||||||
smart => l10n.diskHealth,
|
net => l10n.net,
|
||||||
net => l10n.net,
|
sensor => l10n.sensors,
|
||||||
sensor => l10n.sensors,
|
temp => l10n.temperature,
|
||||||
temp => l10n.temperature,
|
battery => l10n.battery,
|
||||||
battery => l10n.battery,
|
pve => 'PVE',
|
||||||
pve => 'PVE',
|
custom => l10n.cmd,
|
||||||
custom => l10n.cmd,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/// If:
|
/// If:
|
||||||
/// Version 1 => user set [about], default is [about, cpu]
|
/// Version 1 => user set [about], default is [about, cpu]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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/system.dart';
|
|
||||||
import 'package:server_box/data/provider/server.dart';
|
import 'package:server_box/data/provider/server.dart';
|
||||||
|
|
||||||
import 'package:server_box/data/res/build_data.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
enum ShellFunc {
|
enum ShellFunc {
|
||||||
status,
|
status,
|
||||||
@@ -9,19 +10,14 @@ enum ShellFunc {
|
|||||||
process,
|
process,
|
||||||
shutdown,
|
shutdown,
|
||||||
reboot,
|
reboot,
|
||||||
suspend;
|
suspend,
|
||||||
|
;
|
||||||
|
|
||||||
static const seperator = 'SrvBoxSep';
|
static const seperator = 'SrvBoxSep';
|
||||||
|
|
||||||
/// The suffix `\t` is for formatting
|
/// The suffix `\t` is for formatting
|
||||||
static const cmdDivider = '\necho $seperator\n\t';
|
static const cmdDivider = '\necho $seperator\n\t';
|
||||||
|
|
||||||
/// Cached Linux status commands string
|
|
||||||
static final _linuxStatusCmds = StatusCmdType.values.map((e) => e.cmd).join(cmdDivider);
|
|
||||||
|
|
||||||
/// Cached BSD status commands string
|
|
||||||
static final _bsdStatusCmds = BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider);
|
|
||||||
|
|
||||||
/// srvboxm -> ServerBox Mobile
|
/// srvboxm -> ServerBox Mobile
|
||||||
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||||
static const scriptDirHome = '~/.config/server_box';
|
static const scriptDirHome = '~/.config/server_box';
|
||||||
@@ -34,17 +30,19 @@ 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 = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
final customScriptDir =
|
||||||
|
ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||||
if (customScriptDir != null) return customScriptDir;
|
if (customScriptDir != null) return customScriptDir;
|
||||||
_scriptDirMap[id] ??= scriptDirTmp;
|
return _scriptDirMap.putIfAbsent(id, () {
|
||||||
return _scriptDirMap[id]!;
|
return scriptDirTmp;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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';
|
||||||
@@ -61,34 +59,53 @@ 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';
|
||||||
|
|
||||||
String get name => switch (this) {
|
String get name {
|
||||||
ShellFunc.status => 'status',
|
switch (this) {
|
||||||
ShellFunc.process => 'process',
|
case ShellFunc.status:
|
||||||
ShellFunc.shutdown => 'ShutDown',
|
return 'status';
|
||||||
ShellFunc.reboot => 'Reboot',
|
// case ShellFunc.docker:
|
||||||
ShellFunc.suspend => 'Suspend',
|
// // `dockeR` -> avoid conflict with `docker` command
|
||||||
};
|
// return 'dockeR';
|
||||||
|
case ShellFunc.process:
|
||||||
|
return 'process';
|
||||||
|
case ShellFunc.shutdown:
|
||||||
|
return 'ShutDown';
|
||||||
|
case ShellFunc.reboot:
|
||||||
|
return 'Reboot';
|
||||||
|
case ShellFunc.suspend:
|
||||||
|
return 'Suspend';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String get _cmd => switch (this) {
|
String get _cmd {
|
||||||
ShellFunc.status =>
|
switch (this) {
|
||||||
'''
|
case ShellFunc.status:
|
||||||
|
return '''
|
||||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||||
\t$_linuxStatusCmds
|
\t${StatusCmdType.values.map((e) => e.cmd).join(cmdDivider)}
|
||||||
else
|
else
|
||||||
\t$_bsdStatusCmds
|
\t${BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider)}
|
||||||
fi''',
|
fi''';
|
||||||
ShellFunc.process =>
|
// case ShellFunc.docker:
|
||||||
'''
|
// return '''
|
||||||
|
// result=\$(docker version 2>&1 | grep "permission denied")
|
||||||
|
// if [ "\$result" != "" ]; then
|
||||||
|
// \t${_dockerCmds.join(_cmdDivider)}
|
||||||
|
// else
|
||||||
|
// \t${_dockerCmds.map((e) => "sudo -S $e").join(_cmdDivider)}
|
||||||
|
// fi''';
|
||||||
|
case ShellFunc.process:
|
||||||
|
return '''
|
||||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||||
\tif [ "\$isBusybox" != "" ]; then
|
\tif [ "\$isBusybox" != "" ]; then
|
||||||
\t\tps w
|
\t\tps w
|
||||||
@@ -98,29 +115,30 @@ if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
|||||||
else
|
else
|
||||||
\tps -ax
|
\tps -ax
|
||||||
fi
|
fi
|
||||||
''',
|
''';
|
||||||
ShellFunc.shutdown =>
|
case ShellFunc.shutdown:
|
||||||
'''
|
return '''
|
||||||
if [ "\$userId" = "0" ]; then
|
if [ "\$userId" = "0" ]; then
|
||||||
\tshutdown -h now
|
\tshutdown -h now
|
||||||
else
|
else
|
||||||
\tsudo -S shutdown -h now
|
\tsudo -S shutdown -h now
|
||||||
fi''',
|
fi''';
|
||||||
ShellFunc.reboot =>
|
case ShellFunc.reboot:
|
||||||
'''
|
return '''
|
||||||
if [ "\$userId" = "0" ]; then
|
if [ "\$userId" = "0" ]; then
|
||||||
\treboot
|
\treboot
|
||||||
else
|
else
|
||||||
\tsudo -S reboot
|
\tsudo -S reboot
|
||||||
fi''',
|
fi''';
|
||||||
ShellFunc.suspend =>
|
case ShellFunc.suspend:
|
||||||
'''
|
return '''
|
||||||
if [ "\$userId" = "0" ]; then
|
if [ "\$userId" = "0" ]; then
|
||||||
\tsystemctl suspend
|
\tsystemctl suspend
|
||||||
else
|
else
|
||||||
\tsudo -S systemctl suspend
|
\tsudo -S systemctl suspend
|
||||||
fi''',
|
fi''';
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String allScript(Map<String, String>? customCmds) {
|
static String allScript(Map<String, String>? customCmds) {
|
||||||
final sb = StringBuffer();
|
final sb = StringBuffer();
|
||||||
@@ -146,7 +164,9 @@ exec 2>/dev/null
|
|||||||
// Write each func
|
// Write each func
|
||||||
for (final func in values) {
|
for (final func in values) {
|
||||||
final customCmdsStr = () {
|
final customCmdsStr = () {
|
||||||
if (func == ShellFunc.status && customCmds != null && customCmds.isNotEmpty) {
|
if (func == ShellFunc.status &&
|
||||||
|
customCmds != null &&
|
||||||
|
customCmds.isNotEmpty) {
|
||||||
return '$cmdDivider\n\t${customCmds.values.join(cmdDivider)}';
|
return '$cmdDivider\n\t${customCmds.values.join(cmdDivider)}';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
@@ -189,22 +209,22 @@ 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._('lsblk --bytes --json --output FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID'),
|
disk._('df'),
|
||||||
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._('for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
battery._(
|
||||||
|
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||||
nvidia._('nvidia-smi -q -x'),
|
nvidia._('nvidia-smi -q -x'),
|
||||||
amd._('if command -v amd-smi >/dev/null 2>&1; then amd-smi list --json && amd-smi metric --json; elif command -v rocm-smi >/dev/null 2>&1; then rocm-smi --json || rocm-smi --showunique --showuse --showtemp --showfan --showclocks --showmemuse --showpower; elif command -v radeontop >/dev/null 2>&1; then timeout 2s radeontop -d - -l 1 | tail -n +2; else echo "No AMD GPU monitoring tools found"; fi'),
|
|
||||||
sensors._('sensors'),
|
sensors._('sensors'),
|
||||||
diskSmart._('for d in \$(lsblk -dn -o KNAME); do smartctl -a -j /dev/\$d; echo; done'),
|
cpuBrand._('cat /proc/cpuinfo | grep "model name"'),
|
||||||
cpuBrand._('cat /proc/cpuinfo | grep "model name"');
|
;
|
||||||
|
|
||||||
final String cmd;
|
final String cmd;
|
||||||
|
|
||||||
@@ -218,12 +238,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;
|
||||||
|
|
||||||
@@ -232,12 +252,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,
|
||||||
StatusCmdType.sensors => l10n.sensors,
|
final val => val.name,
|
||||||
StatusCmdType.disk => l10n.disk,
|
};
|
||||||
final val => val.name,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
24
lib/data/model/app/sync.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -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/tab.dart';
|
import 'package:server_box/view/page/server/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,41 +56,7 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +56,8 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,6 @@ 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:
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
/// AMD GPU monitoring data structures
|
|
||||||
/// Supports both amd-smi and rocm-smi tools
|
|
||||||
/// Example JSON output:
|
|
||||||
/// [
|
|
||||||
/// {
|
|
||||||
/// "name": "AMD Radeon RX 7900 XTX",
|
|
||||||
/// "device_id": "0",
|
|
||||||
/// "temp": 45,
|
|
||||||
/// "power": "120W / 355W",
|
|
||||||
/// "memory": {
|
|
||||||
/// "total": 24576,
|
|
||||||
/// "used": 1024,
|
|
||||||
/// "unit": "MB",
|
|
||||||
/// "processes": [
|
|
||||||
/// {
|
|
||||||
/// "pid": 2456,
|
|
||||||
/// "name": "firefox",
|
|
||||||
/// "memory": 512
|
|
||||||
/// }
|
|
||||||
/// ]
|
|
||||||
/// },
|
|
||||||
/// "utilization": 75,
|
|
||||||
/// "fan_speed": 1200,
|
|
||||||
/// "clock_speed": 2400
|
|
||||||
/// }
|
|
||||||
/// ]
|
|
||||||
|
|
||||||
class AmdSmi {
|
|
||||||
static List<AmdSmiItem> fromJson(String raw) {
|
|
||||||
try {
|
|
||||||
final jsonData = json.decode(raw);
|
|
||||||
if (jsonData is! List) return [];
|
|
||||||
|
|
||||||
return jsonData
|
|
||||||
.map((gpu) => _parseGpuItem(gpu))
|
|
||||||
.where((item) => item != null)
|
|
||||||
.cast<AmdSmiItem>()
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static AmdSmiItem? _parseGpuItem(Map<String, dynamic> gpu) {
|
|
||||||
try {
|
|
||||||
final name = gpu['name'] ?? gpu['card_model'] ?? gpu['device_name'] ?? 'Unknown AMD GPU';
|
|
||||||
final deviceId = gpu['device_id']?.toString() ?? gpu['gpu_id']?.toString() ?? '0';
|
|
||||||
|
|
||||||
// Temperature parsing
|
|
||||||
final tempRaw = gpu['temperature'] ?? gpu['temp'] ?? gpu['gpu_temp'];
|
|
||||||
final temp = _parseIntValue(tempRaw);
|
|
||||||
|
|
||||||
// Power parsing
|
|
||||||
final powerDraw = gpu['power_draw'] ?? gpu['current_power'];
|
|
||||||
final powerCap = gpu['power_cap'] ?? gpu['power_limit'] ?? gpu['max_power'];
|
|
||||||
final power = _formatPower(powerDraw, powerCap);
|
|
||||||
|
|
||||||
// Memory parsing
|
|
||||||
final memory = _parseMemory(gpu['memory'] ?? gpu['vram'] ?? {});
|
|
||||||
|
|
||||||
// Utilization parsing
|
|
||||||
final utilization = _parseIntValue(gpu['utilization'] ?? gpu['gpu_util'] ?? gpu['activity']);
|
|
||||||
|
|
||||||
// Fan speed parsing
|
|
||||||
final fanSpeed = _parseIntValue(gpu['fan_speed'] ?? gpu['fan_rpm']);
|
|
||||||
|
|
||||||
// Clock speed parsing
|
|
||||||
final clockSpeed = _parseIntValue(gpu['clock_speed'] ?? gpu['gpu_clock'] ?? gpu['sclk']);
|
|
||||||
|
|
||||||
return AmdSmiItem(
|
|
||||||
deviceId: deviceId,
|
|
||||||
name: name,
|
|
||||||
temp: temp,
|
|
||||||
power: power,
|
|
||||||
memory: memory,
|
|
||||||
utilization: utilization,
|
|
||||||
fanSpeed: fanSpeed,
|
|
||||||
clockSpeed: clockSpeed,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _parseIntValue(dynamic value) {
|
|
||||||
if (value == null) return 0;
|
|
||||||
if (value is int) return value;
|
|
||||||
if (value is String) {
|
|
||||||
// Remove units and parse (e.g., "45°C" -> 45, "1200 RPM" -> 1200)
|
|
||||||
final cleanValue = value.replaceAll(RegExp(r'[^\d]'), '');
|
|
||||||
return int.tryParse(cleanValue) ?? 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _formatPower(dynamic draw, dynamic cap) {
|
|
||||||
final drawValue = _parseIntValue(draw);
|
|
||||||
final capValue = _parseIntValue(cap);
|
|
||||||
|
|
||||||
if (drawValue == 0 && capValue == 0) return 'N/A';
|
|
||||||
if (capValue == 0) return '${drawValue}W';
|
|
||||||
return '${drawValue}W / ${capValue}W';
|
|
||||||
}
|
|
||||||
|
|
||||||
static AmdSmiMem _parseMemory(Map<String, dynamic> memData) {
|
|
||||||
final total = _parseIntValue(memData['total'] ?? memData['total_memory']);
|
|
||||||
final used = _parseIntValue(memData['used'] ?? memData['used_memory']);
|
|
||||||
final unit = memData['unit']?.toString() ?? 'MB';
|
|
||||||
|
|
||||||
final processes = <AmdSmiMemProcess>[];
|
|
||||||
final processesData = memData['processes'];
|
|
||||||
if (processesData is List) {
|
|
||||||
for (final proc in processesData) {
|
|
||||||
if (proc is Map<String, dynamic>) {
|
|
||||||
final process = _parseProcess(proc);
|
|
||||||
if (process != null) processes.add(process);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AmdSmiMem(total, used, unit, processes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AmdSmiMemProcess? _parseProcess(Map<String, dynamic> procData) {
|
|
||||||
final pid = _parseIntValue(procData['pid']);
|
|
||||||
final name = procData['name']?.toString() ?? procData['process_name']?.toString() ?? 'Unknown';
|
|
||||||
final memory = _parseIntValue(procData['memory'] ?? procData['used_memory']);
|
|
||||||
|
|
||||||
if (pid == 0) return null;
|
|
||||||
return AmdSmiMemProcess(pid, name, memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AmdSmiItem {
|
|
||||||
final String deviceId;
|
|
||||||
final String name;
|
|
||||||
final int temp;
|
|
||||||
final String power;
|
|
||||||
final AmdSmiMem memory;
|
|
||||||
final int utilization;
|
|
||||||
final int fanSpeed;
|
|
||||||
final int clockSpeed;
|
|
||||||
|
|
||||||
const AmdSmiItem({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.name,
|
|
||||||
required this.temp,
|
|
||||||
required this.power,
|
|
||||||
required this.memory,
|
|
||||||
required this.utilization,
|
|
||||||
required this.fanSpeed,
|
|
||||||
required this.clockSpeed,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AmdSmiItem{name: $name, temp: $temp, power: $power, utilization: $utilization%, memory: $memory}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AmdSmiMem {
|
|
||||||
final int total;
|
|
||||||
final int used;
|
|
||||||
final String unit;
|
|
||||||
final List<AmdSmiMemProcess> processes;
|
|
||||||
|
|
||||||
const AmdSmiMem(this.total, this.used, this.unit, this.processes);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AmdSmiMem{total: $total, used: $used, unit: $unit, processes: ${processes.length}}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AmdSmiMemProcess {
|
|
||||||
final int pid;
|
|
||||||
final String name;
|
|
||||||
final int memory;
|
|
||||||
|
|
||||||
const AmdSmiMemProcess(this.pid, this.name, this.memory);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AmdSmiMemProcess{pid: $pid, name: $name, memory: $memory}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,32 @@
|
|||||||
|
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(includeIfNull: false)
|
@JsonSerializable()
|
||||||
|
@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({
|
||||||
@@ -35,7 +40,8 @@ final class ServerCustom {
|
|||||||
this.scriptDir,
|
this.scriptDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ServerCustom.fromJson(Map<String, dynamic> json) => _$ServerCustomFromJson(json);
|
factory ServerCustom.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ServerCustomFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$ServerCustomToJson(this);
|
Map<String, dynamic> toJson() => _$ServerCustomToJson(this);
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,85 @@
|
|||||||
|
|
||||||
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>{
|
||||||
if (instance.pveAddr case final value?) 'pveAddr': value,
|
'pveAddr': instance.pveAddr,
|
||||||
'pveIgnoreCert': instance.pveIgnoreCert,
|
'pveIgnoreCert': instance.pveIgnoreCert,
|
||||||
if (instance.cmds case final value?) 'cmds': value,
|
'cmds': instance.cmds,
|
||||||
if (instance.preferTempDev case final value?) 'preferTempDev': value,
|
'preferTempDev': instance.preferTempDev,
|
||||||
if (instance.logoUrl case final value?) 'logoUrl': value,
|
'logoUrl': instance.logoUrl,
|
||||||
if (instance.netDev case final value?) 'netDev': value,
|
'netDev': instance.netDev,
|
||||||
if (instance.scriptDir case final value?) 'scriptDir': value,
|
'scriptDir': instance.scriptDir,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,208 +1,29 @@
|
|||||||
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 with EquatableMixin {
|
class Disk {
|
||||||
final String path;
|
final String fs;
|
||||||
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.path,
|
required this.fs,
|
||||||
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');
|
||||||
if (items.isNotEmpty) items.removeAt(0);
|
items.removeAt(0);
|
||||||
var pathCache = '';
|
var pathCache = '';
|
||||||
for (var item in items) {
|
for (var item in items) {
|
||||||
if (item.isEmpty) {
|
if (item.isEmpty) {
|
||||||
@@ -222,12 +43,12 @@ class Disk with EquatableMixin {
|
|||||||
final mount = vals[5];
|
final mount = vals[5];
|
||||||
if (!_shouldCalc(fs, mount)) continue;
|
if (!_shouldCalc(fs, mount)) continue;
|
||||||
list.add(Disk(
|
list.add(Disk(
|
||||||
path: fs,
|
fs: fs,
|
||||||
mount: mount,
|
mount: mount,
|
||||||
usedPercent: int.parse(vals[4].replaceFirst('%', '')),
|
usedPercent: int.parse(vals[4].replaceFirst('%', '')),
|
||||||
used: BigInt.parse(vals[2]) ~/ BigInt.from(1024),
|
used: BigInt.parse(vals[2]),
|
||||||
size: BigInt.parse(vals[1]) ~/ BigInt.from(1024),
|
size: BigInt.parse(vals[1]),
|
||||||
avail: BigInt.parse(vals[3]) ~/ BigInt.from(1024),
|
avail: BigInt.parse(vals[3]),
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue;
|
continue;
|
||||||
@@ -237,8 +58,9 @@ class Disk with EquatableMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props =>
|
String toString() {
|
||||||
[path, name, kname, fsTyp, mount, usedPercent, used, size, avail, uuid, children];
|
return 'Disk{dev: $fs, mount: $mount, usedPercent: $usedPercent, used: $used, size: $size, avail: $avail}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiskIO extends TimeSeq<List<DiskIOPiece>> {
|
class DiskIO extends TimeSeq<List<DiskIOPiece>> {
|
||||||
@@ -250,16 +72,9 @@ class DiskIO extends TimeSeq<List<DiskIOPiece>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(double?, double?) _getSpeed(String dev) {
|
(double?, double?) _getSpeed(String dev) {
|
||||||
// Extract the device name from path if needed
|
if (dev.startsWith('/dev/')) dev = dev.substring(5);
|
||||||
String searchDev = dev;
|
final old = pre.firstWhereOrNull((e) => e.dev == dev);
|
||||||
if (dev.startsWith('/dev/')) {
|
final new_ = now.firstWhereOrNull((e) => e.dev == 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;
|
||||||
@@ -289,14 +104,11 @@ 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')) {
|
!item.dev.startsWith('sr')) continue;
|
||||||
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);
|
||||||
@@ -354,11 +166,7 @@ class DiskUsage {
|
|||||||
required this.size,
|
required this.size,
|
||||||
});
|
});
|
||||||
|
|
||||||
double get usedPercent {
|
double get usedPercent => used / size * 100;
|
||||||
// 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) {
|
||||||
@@ -366,12 +174,9 @@ 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.path, disk.mount)) continue;
|
if (!_shouldCalc(disk.fs, disk.mount)) continue;
|
||||||
// Use a combination of path and kernel name to uniquely identify disks
|
if (devs.contains(disk.fs)) continue;
|
||||||
// This helps distinguish between multiple physical disks in BTRFS RAID setups
|
devs.add(disk.fs);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -380,24 +185,12 @@ 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') ||
|
||||||
// Include common filesystem types
|
// fs.startsWith('overlay') ||
|
||||||
// final commonFsTypes = ['ext2', 'ext3', 'ext4', 'xfs', 'btrfs', 'zfs', 'ntfs', 'fat', 'vfat'];
|
// fs.startsWith('tmpfs')) return false;
|
||||||
// if (commonFsTypes.any((type) => fs.toLowerCase() == type)) return true;
|
return false;
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
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
|
|
||||||
abstract 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() ?? '';
|
|
||||||
|
|
||||||
if (!_isPhysicalDisk(device)) continue;
|
|
||||||
|
|
||||||
final healthy = _parseHealthStatus(data);
|
|
||||||
|
|
||||||
// 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 bool _isPhysicalDisk(String device) {
|
|
||||||
if (device.isEmpty) return false;
|
|
||||||
|
|
||||||
// Common patterns for physical disks
|
|
||||||
final patterns = [
|
|
||||||
RegExp(r'^/dev/sd[a-z]$'), // SATA/SCSI: /dev/sda, /dev/sdb
|
|
||||||
RegExp(r'^/dev/hd[a-z]$'), // IDE: /dev/hda, /dev/hdb
|
|
||||||
RegExp(r'^/dev/nvme\d+n\d+$'), // NVMe: /dev/nvme0n1, /dev/nvme1n1
|
|
||||||
RegExp(r'^/dev/mmcblk\d+$'), // MMC: /dev/mmcblk0
|
|
||||||
RegExp(r'^/dev/vd[a-z]$'), // VirtIO: /dev/vda, /dev/vdb
|
|
||||||
RegExp(r'^/dev/xvd[a-z]$'), // Xen: /dev/xvda, /dev/xvdb
|
|
||||||
];
|
|
||||||
|
|
||||||
return patterns.any((pattern) => pattern.hasMatch(device));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool? _parseHealthStatus(Map<String, dynamic> data) {
|
|
||||||
// smart_status.passed
|
|
||||||
final smartStatus = data['smart_status'];
|
|
||||||
if (smartStatus is Map<String, dynamic>) {
|
|
||||||
final passed = smartStatus['passed'];
|
|
||||||
if (passed is bool) return passed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// smart_status.status
|
|
||||||
if (smartStatus is Map<String, dynamic>) {
|
|
||||||
final status = smartStatus['status']?.toString().toLowerCase();
|
|
||||||
if (status != null) {
|
|
||||||
if (status.contains('pass') || status.contains('ok')) return true;
|
|
||||||
if (status.contains('fail')) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// smart_status
|
|
||||||
final rootSmartStatus = data['smart_status']?.toString().toLowerCase();
|
|
||||||
if (rootSmartStatus != null) {
|
|
||||||
if (rootSmartStatus.contains('pass') || rootSmartStatus.contains('ok')) return true;
|
|
||||||
if (rootSmartStatus.contains('fail')) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// health attrs
|
|
||||||
final attrTable = data['ata_smart_attributes']?['table'] as List?;
|
|
||||||
if (attrTable != null) {
|
|
||||||
var hasFailingAttributes = false;
|
|
||||||
|
|
||||||
for (final attr in attrTable) {
|
|
||||||
if (attr is Map<String, dynamic>) {
|
|
||||||
final whenFailed = attr['when_failed']?.toString();
|
|
||||||
if (whenFailed != null && whenFailed.isNotEmpty && whenFailed != 'never') {
|
|
||||||
hasFailingAttributes = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether the attribute is critical
|
|
||||||
final name = attr['name']?.toString();
|
|
||||||
final value = attr['value'] as int?;
|
|
||||||
final thresh = attr['thresh'] as int?;
|
|
||||||
|
|
||||||
if (name != null && value != null && thresh != null && thresh > 0) {
|
|
||||||
const criticalAttrs = [
|
|
||||||
'Reallocated_Sector_Ct',
|
|
||||||
'Reallocated_Event_Count',
|
|
||||||
'Current_Pending_Sector',
|
|
||||||
'Offline_Uncorrectable',
|
|
||||||
'UDMA_CRC_Error_Count',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (criticalAttrs.contains(name) && value < thresh) {
|
|
||||||
hasFailingAttributes = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFailingAttributes) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attrTable != null && attrTable.isNotEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncertain status, assume healthy
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
abstract 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
|
|
||||||
abstract 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)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// 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 'disk_smart.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$DiskSmart {
|
|
||||||
|
|
||||||
String get device; bool? get healthy; double? get temperature; String? get model; String? get serial; int? get powerOnHours; int? get powerCycleCount; Map<String, dynamic> get rawData; Map<String, SmartAttribute> get smartAttributes;
|
|
||||||
/// Create a copy of DiskSmart
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$DiskSmartCopyWith<DiskSmart> get copyWith => _$DiskSmartCopyWithImpl<DiskSmart>(this as DiskSmart, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this DiskSmart to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is DiskSmart&&(identical(other.device, device) || other.device == device)&&(identical(other.healthy, healthy) || other.healthy == healthy)&&(identical(other.temperature, temperature) || other.temperature == temperature)&&(identical(other.model, model) || other.model == model)&&(identical(other.serial, serial) || other.serial == serial)&&(identical(other.powerOnHours, powerOnHours) || other.powerOnHours == powerOnHours)&&(identical(other.powerCycleCount, powerCycleCount) || other.powerCycleCount == powerCycleCount)&&const DeepCollectionEquality().equals(other.rawData, rawData)&&const DeepCollectionEquality().equals(other.smartAttributes, smartAttributes));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,device,healthy,temperature,model,serial,powerOnHours,powerCycleCount,const DeepCollectionEquality().hash(rawData),const DeepCollectionEquality().hash(smartAttributes));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $DiskSmartCopyWith<$Res> {
|
|
||||||
factory $DiskSmartCopyWith(DiskSmart value, $Res Function(DiskSmart) _then) = _$DiskSmartCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
String device, bool? healthy, double? temperature, String? model, String? serial, int? powerOnHours, int? powerCycleCount, Map<String, dynamic> rawData, Map<String, SmartAttribute> smartAttributes
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$DiskSmartCopyWithImpl<$Res>
|
|
||||||
implements $DiskSmartCopyWith<$Res> {
|
|
||||||
_$DiskSmartCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final DiskSmart _self;
|
|
||||||
final $Res Function(DiskSmart) _then;
|
|
||||||
|
|
||||||
/// Create a copy of DiskSmart
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? device = null,Object? healthy = freezed,Object? temperature = freezed,Object? model = freezed,Object? serial = freezed,Object? powerOnHours = freezed,Object? powerCycleCount = freezed,Object? rawData = null,Object? smartAttributes = null,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
device: null == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,healthy: freezed == healthy ? _self.healthy : healthy // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,temperature: freezed == temperature ? _self.temperature : temperature // ignore: cast_nullable_to_non_nullable
|
|
||||||
as double?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,serial: freezed == serial ? _self.serial : serial // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,powerOnHours: freezed == powerOnHours ? _self.powerOnHours : powerOnHours // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,powerCycleCount: freezed == powerCycleCount ? _self.powerCycleCount : powerCycleCount // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,rawData: null == rawData ? _self.rawData : rawData // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,smartAttributes: null == smartAttributes ? _self.smartAttributes : smartAttributes // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, SmartAttribute>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _DiskSmart extends DiskSmart {
|
|
||||||
const _DiskSmart({required this.device, this.healthy, this.temperature, this.model, this.serial, this.powerOnHours, this.powerCycleCount, required final Map<String, dynamic> rawData, required final Map<String, SmartAttribute> smartAttributes}): _rawData = rawData,_smartAttributes = smartAttributes,super._();
|
|
||||||
factory _DiskSmart.fromJson(Map<String, dynamic> json) => _$DiskSmartFromJson(json);
|
|
||||||
|
|
||||||
@override final String device;
|
|
||||||
@override final bool? healthy;
|
|
||||||
@override final double? temperature;
|
|
||||||
@override final String? model;
|
|
||||||
@override final String? serial;
|
|
||||||
@override final int? powerOnHours;
|
|
||||||
@override final int? powerCycleCount;
|
|
||||||
final Map<String, dynamic> _rawData;
|
|
||||||
@override Map<String, dynamic> get rawData {
|
|
||||||
if (_rawData is EqualUnmodifiableMapView) return _rawData;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableMapView(_rawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, SmartAttribute> _smartAttributes;
|
|
||||||
@override Map<String, SmartAttribute> get smartAttributes {
|
|
||||||
if (_smartAttributes is EqualUnmodifiableMapView) return _smartAttributes;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableMapView(_smartAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of DiskSmart
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$DiskSmartCopyWith<_DiskSmart> get copyWith => __$DiskSmartCopyWithImpl<_DiskSmart>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$DiskSmartToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DiskSmart&&(identical(other.device, device) || other.device == device)&&(identical(other.healthy, healthy) || other.healthy == healthy)&&(identical(other.temperature, temperature) || other.temperature == temperature)&&(identical(other.model, model) || other.model == model)&&(identical(other.serial, serial) || other.serial == serial)&&(identical(other.powerOnHours, powerOnHours) || other.powerOnHours == powerOnHours)&&(identical(other.powerCycleCount, powerCycleCount) || other.powerCycleCount == powerCycleCount)&&const DeepCollectionEquality().equals(other._rawData, _rawData)&&const DeepCollectionEquality().equals(other._smartAttributes, _smartAttributes));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,device,healthy,temperature,model,serial,powerOnHours,powerCycleCount,const DeepCollectionEquality().hash(_rawData),const DeepCollectionEquality().hash(_smartAttributes));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$DiskSmartCopyWith<$Res> implements $DiskSmartCopyWith<$Res> {
|
|
||||||
factory _$DiskSmartCopyWith(_DiskSmart value, $Res Function(_DiskSmart) _then) = __$DiskSmartCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
String device, bool? healthy, double? temperature, String? model, String? serial, int? powerOnHours, int? powerCycleCount, Map<String, dynamic> rawData, Map<String, SmartAttribute> smartAttributes
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$DiskSmartCopyWithImpl<$Res>
|
|
||||||
implements _$DiskSmartCopyWith<$Res> {
|
|
||||||
__$DiskSmartCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _DiskSmart _self;
|
|
||||||
final $Res Function(_DiskSmart) _then;
|
|
||||||
|
|
||||||
/// Create a copy of DiskSmart
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? device = null,Object? healthy = freezed,Object? temperature = freezed,Object? model = freezed,Object? serial = freezed,Object? powerOnHours = freezed,Object? powerCycleCount = freezed,Object? rawData = null,Object? smartAttributes = null,}) {
|
|
||||||
return _then(_DiskSmart(
|
|
||||||
device: null == device ? _self.device : device // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,healthy: freezed == healthy ? _self.healthy : healthy // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,temperature: freezed == temperature ? _self.temperature : temperature // ignore: cast_nullable_to_non_nullable
|
|
||||||
as double?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,serial: freezed == serial ? _self.serial : serial // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,powerOnHours: freezed == powerOnHours ? _self.powerOnHours : powerOnHours // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,powerCycleCount: freezed == powerCycleCount ? _self.powerCycleCount : powerCycleCount // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,rawData: null == rawData ? _self._rawData : rawData // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,smartAttributes: null == smartAttributes ? _self._smartAttributes : smartAttributes // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, SmartAttribute>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SmartAttribute {
|
|
||||||
|
|
||||||
int? get id; String get name; int? get value; int? get worst; int? get thresh; String? get whenFailed; dynamic get rawValue; String? get rawString; SmartAttributeFlags get flags;
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SmartAttributeCopyWith<SmartAttribute> get copyWith => _$SmartAttributeCopyWithImpl<SmartAttribute>(this as SmartAttribute, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SmartAttribute to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmartAttribute&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.value, value) || other.value == value)&&(identical(other.worst, worst) || other.worst == worst)&&(identical(other.thresh, thresh) || other.thresh == thresh)&&(identical(other.whenFailed, whenFailed) || other.whenFailed == whenFailed)&&const DeepCollectionEquality().equals(other.rawValue, rawValue)&&(identical(other.rawString, rawString) || other.rawString == rawString)&&(identical(other.flags, flags) || other.flags == flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,value,worst,thresh,whenFailed,const DeepCollectionEquality().hash(rawValue),rawString,flags);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SmartAttributeCopyWith<$Res> {
|
|
||||||
factory $SmartAttributeCopyWith(SmartAttribute value, $Res Function(SmartAttribute) _then) = _$SmartAttributeCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
int? id, String name, int? value, int? worst, int? thresh, String? whenFailed, dynamic rawValue, String? rawString, SmartAttributeFlags flags
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$SmartAttributeFlagsCopyWith<$Res> get flags;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SmartAttributeCopyWithImpl<$Res>
|
|
||||||
implements $SmartAttributeCopyWith<$Res> {
|
|
||||||
_$SmartAttributeCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SmartAttribute _self;
|
|
||||||
final $Res Function(SmartAttribute) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = null,Object? value = freezed,Object? worst = freezed,Object? thresh = freezed,Object? whenFailed = freezed,Object? rawValue = freezed,Object? rawString = freezed,Object? flags = null,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,worst: freezed == worst ? _self.worst : worst // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,thresh: freezed == thresh ? _self.thresh : thresh // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,whenFailed: freezed == whenFailed ? _self.whenFailed : whenFailed // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,rawValue: freezed == rawValue ? _self.rawValue : rawValue // ignore: cast_nullable_to_non_nullable
|
|
||||||
as dynamic,rawString: freezed == rawString ? _self.rawString : rawString // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,flags: null == flags ? _self.flags : flags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SmartAttributeFlags,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SmartAttributeFlagsCopyWith<$Res> get flags {
|
|
||||||
|
|
||||||
return $SmartAttributeFlagsCopyWith<$Res>(_self.flags, (value) {
|
|
||||||
return _then(_self.copyWith(flags: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _SmartAttribute extends SmartAttribute {
|
|
||||||
const _SmartAttribute({this.id, required this.name, this.value, this.worst, this.thresh, this.whenFailed, this.rawValue, this.rawString, required this.flags}): super._();
|
|
||||||
factory _SmartAttribute.fromJson(Map<String, dynamic> json) => _$SmartAttributeFromJson(json);
|
|
||||||
|
|
||||||
@override final int? id;
|
|
||||||
@override final String name;
|
|
||||||
@override final int? value;
|
|
||||||
@override final int? worst;
|
|
||||||
@override final int? thresh;
|
|
||||||
@override final String? whenFailed;
|
|
||||||
@override final dynamic rawValue;
|
|
||||||
@override final String? rawString;
|
|
||||||
@override final SmartAttributeFlags flags;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SmartAttributeCopyWith<_SmartAttribute> get copyWith => __$SmartAttributeCopyWithImpl<_SmartAttribute>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SmartAttributeToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SmartAttribute&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.value, value) || other.value == value)&&(identical(other.worst, worst) || other.worst == worst)&&(identical(other.thresh, thresh) || other.thresh == thresh)&&(identical(other.whenFailed, whenFailed) || other.whenFailed == whenFailed)&&const DeepCollectionEquality().equals(other.rawValue, rawValue)&&(identical(other.rawString, rawString) || other.rawString == rawString)&&(identical(other.flags, flags) || other.flags == flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,value,worst,thresh,whenFailed,const DeepCollectionEquality().hash(rawValue),rawString,flags);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SmartAttributeCopyWith<$Res> implements $SmartAttributeCopyWith<$Res> {
|
|
||||||
factory _$SmartAttributeCopyWith(_SmartAttribute value, $Res Function(_SmartAttribute) _then) = __$SmartAttributeCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
int? id, String name, int? value, int? worst, int? thresh, String? whenFailed, dynamic rawValue, String? rawString, SmartAttributeFlags flags
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
@override $SmartAttributeFlagsCopyWith<$Res> get flags;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SmartAttributeCopyWithImpl<$Res>
|
|
||||||
implements _$SmartAttributeCopyWith<$Res> {
|
|
||||||
__$SmartAttributeCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SmartAttribute _self;
|
|
||||||
final $Res Function(_SmartAttribute) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = null,Object? value = freezed,Object? worst = freezed,Object? thresh = freezed,Object? whenFailed = freezed,Object? rawValue = freezed,Object? rawString = freezed,Object? flags = null,}) {
|
|
||||||
return _then(_SmartAttribute(
|
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,worst: freezed == worst ? _self.worst : worst // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,thresh: freezed == thresh ? _self.thresh : thresh // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,whenFailed: freezed == whenFailed ? _self.whenFailed : whenFailed // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,rawValue: freezed == rawValue ? _self.rawValue : rawValue // ignore: cast_nullable_to_non_nullable
|
|
||||||
as dynamic,rawString: freezed == rawString ? _self.rawString : rawString // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,flags: null == flags ? _self.flags : flags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SmartAttributeFlags,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttribute
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SmartAttributeFlagsCopyWith<$Res> get flags {
|
|
||||||
|
|
||||||
return $SmartAttributeFlagsCopyWith<$Res>(_self.flags, (value) {
|
|
||||||
return _then(_self.copyWith(flags: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SmartAttributeFlags {
|
|
||||||
|
|
||||||
int? get value; String? get string; bool get prefailure; bool get updatedOnline; bool get performance; bool get errorRate; bool get eventCount; bool get autoKeep;
|
|
||||||
/// Create a copy of SmartAttributeFlags
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SmartAttributeFlagsCopyWith<SmartAttributeFlags> get copyWith => _$SmartAttributeFlagsCopyWithImpl<SmartAttributeFlags>(this as SmartAttributeFlags, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SmartAttributeFlags to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmartAttributeFlags&&(identical(other.value, value) || other.value == value)&&(identical(other.string, string) || other.string == string)&&(identical(other.prefailure, prefailure) || other.prefailure == prefailure)&&(identical(other.updatedOnline, updatedOnline) || other.updatedOnline == updatedOnline)&&(identical(other.performance, performance) || other.performance == performance)&&(identical(other.errorRate, errorRate) || other.errorRate == errorRate)&&(identical(other.eventCount, eventCount) || other.eventCount == eventCount)&&(identical(other.autoKeep, autoKeep) || other.autoKeep == autoKeep));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,value,string,prefailure,updatedOnline,performance,errorRate,eventCount,autoKeep);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SmartAttributeFlagsCopyWith<$Res> {
|
|
||||||
factory $SmartAttributeFlagsCopyWith(SmartAttributeFlags value, $Res Function(SmartAttributeFlags) _then) = _$SmartAttributeFlagsCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
int? value, String? string, bool prefailure, bool updatedOnline, bool performance, bool errorRate, bool eventCount, bool autoKeep
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SmartAttributeFlagsCopyWithImpl<$Res>
|
|
||||||
implements $SmartAttributeFlagsCopyWith<$Res> {
|
|
||||||
_$SmartAttributeFlagsCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SmartAttributeFlags _self;
|
|
||||||
final $Res Function(SmartAttributeFlags) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttributeFlags
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? value = freezed,Object? string = freezed,Object? prefailure = null,Object? updatedOnline = null,Object? performance = null,Object? errorRate = null,Object? eventCount = null,Object? autoKeep = null,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,string: freezed == string ? _self.string : string // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,prefailure: null == prefailure ? _self.prefailure : prefailure // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,updatedOnline: null == updatedOnline ? _self.updatedOnline : updatedOnline // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,performance: null == performance ? _self.performance : performance // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,errorRate: null == errorRate ? _self.errorRate : errorRate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,eventCount: null == eventCount ? _self.eventCount : eventCount // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,autoKeep: null == autoKeep ? _self.autoKeep : autoKeep // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _SmartAttributeFlags extends SmartAttributeFlags {
|
|
||||||
const _SmartAttributeFlags({this.value, this.string, this.prefailure = false, this.updatedOnline = false, this.performance = false, this.errorRate = false, this.eventCount = false, this.autoKeep = false}): super._();
|
|
||||||
factory _SmartAttributeFlags.fromJson(Map<String, dynamic> json) => _$SmartAttributeFlagsFromJson(json);
|
|
||||||
|
|
||||||
@override final int? value;
|
|
||||||
@override final String? string;
|
|
||||||
@override@JsonKey() final bool prefailure;
|
|
||||||
@override@JsonKey() final bool updatedOnline;
|
|
||||||
@override@JsonKey() final bool performance;
|
|
||||||
@override@JsonKey() final bool errorRate;
|
|
||||||
@override@JsonKey() final bool eventCount;
|
|
||||||
@override@JsonKey() final bool autoKeep;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttributeFlags
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SmartAttributeFlagsCopyWith<_SmartAttributeFlags> get copyWith => __$SmartAttributeFlagsCopyWithImpl<_SmartAttributeFlags>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SmartAttributeFlagsToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SmartAttributeFlags&&(identical(other.value, value) || other.value == value)&&(identical(other.string, string) || other.string == string)&&(identical(other.prefailure, prefailure) || other.prefailure == prefailure)&&(identical(other.updatedOnline, updatedOnline) || other.updatedOnline == updatedOnline)&&(identical(other.performance, performance) || other.performance == performance)&&(identical(other.errorRate, errorRate) || other.errorRate == errorRate)&&(identical(other.eventCount, eventCount) || other.eventCount == eventCount)&&(identical(other.autoKeep, autoKeep) || other.autoKeep == autoKeep));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,value,string,prefailure,updatedOnline,performance,errorRate,eventCount,autoKeep);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SmartAttributeFlagsCopyWith<$Res> implements $SmartAttributeFlagsCopyWith<$Res> {
|
|
||||||
factory _$SmartAttributeFlagsCopyWith(_SmartAttributeFlags value, $Res Function(_SmartAttributeFlags) _then) = __$SmartAttributeFlagsCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
int? value, String? string, bool prefailure, bool updatedOnline, bool performance, bool errorRate, bool eventCount, bool autoKeep
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SmartAttributeFlagsCopyWithImpl<$Res>
|
|
||||||
implements _$SmartAttributeFlagsCopyWith<$Res> {
|
|
||||||
__$SmartAttributeFlagsCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SmartAttributeFlags _self;
|
|
||||||
final $Res Function(_SmartAttributeFlags) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SmartAttributeFlags
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? value = freezed,Object? string = freezed,Object? prefailure = null,Object? updatedOnline = null,Object? performance = null,Object? errorRate = null,Object? eventCount = null,Object? autoKeep = null,}) {
|
|
||||||
return _then(_SmartAttributeFlags(
|
|
||||||
value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int?,string: freezed == string ? _self.string : string // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,prefailure: null == prefailure ? _self.prefailure : prefailure // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,updatedOnline: null == updatedOnline ? _self.updatedOnline : updatedOnline // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,performance: null == performance ? _self.performance : performance // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,errorRate: null == errorRate ? _self.errorRate : errorRate // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,eventCount: null == eventCount ? _self.eventCount : eventCount // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,autoKeep: null == autoKeep ? _self.autoKeep : autoKeep // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'disk_smart.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_DiskSmart _$DiskSmartFromJson(Map<String, dynamic> json) => _DiskSmart(
|
|
||||||
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> _$DiskSmartToJson(_DiskSmart 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
_SmartAttribute _$SmartAttributeFromJson(Map<String, dynamic> json) =>
|
|
||||||
_SmartAttribute(
|
|
||||||
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> _$SmartAttributeToJson(_SmartAttribute 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
_SmartAttributeFlags _$SmartAttributeFlagsFromJson(Map<String, dynamic> json) =>
|
|
||||||
_SmartAttributeFlags(
|
|
||||||
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> _$SmartAttributeFlagsToJson(
|
|
||||||
_SmartAttributeFlags 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,
|
|
||||||
};
|
|
||||||
@@ -11,7 +11,6 @@ enum Dist {
|
|||||||
alpine,
|
alpine,
|
||||||
rocky,
|
rocky,
|
||||||
deepin,
|
deepin,
|
||||||
coreelec,
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// 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';
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
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({
|
||||||
@@ -13,7 +17,8 @@ class PrivateKeyInfo {
|
|||||||
required this.key,
|
required this.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PrivateKeyInfo.fromJson(Map<String, dynamic> json) => _$PrivateKeyInfoFromJson(json);
|
factory PrivateKeyInfo.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PrivateKeyInfoFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PrivateKeyInfoToJson(this);
|
Map<String, dynamic> toJson() => _$PrivateKeyInfoToJson(this);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,47 @@
|
|||||||
|
|
||||||
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
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@@ -13,4 +54,7 @@ PrivateKeyInfo _$PrivateKeyInfoFromJson(Map<String, dynamic> json) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$PrivateKeyInfoToJson(PrivateKeyInfo instance) =>
|
Map<String, dynamic> _$PrivateKeyInfoToJson(PrivateKeyInfo instance) =>
|
||||||
<String, dynamic>{'id': instance.id, 'private_key': instance.key};
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'private_key': instance.key,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:fl_lib/fl_lib.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/amd.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';
|
||||||
@@ -21,7 +19,12 @@ class Server {
|
|||||||
SSHClient? client;
|
SSHClient? client;
|
||||||
ServerConn conn;
|
ServerConn conn;
|
||||||
|
|
||||||
Server(this.spi, this.status, this.conn, {this.client});
|
Server(
|
||||||
|
this.spi,
|
||||||
|
this.status,
|
||||||
|
this.conn, {
|
||||||
|
this.client,
|
||||||
|
});
|
||||||
|
|
||||||
bool get needGenClient => conn < ServerConn.connecting;
|
bool get needGenClient => conn < ServerConn.connecting;
|
||||||
|
|
||||||
@@ -41,9 +44,7 @@ class ServerStatus {
|
|||||||
SystemType system;
|
SystemType system;
|
||||||
Err? err;
|
Err? err;
|
||||||
DiskIO diskIO;
|
DiskIO diskIO;
|
||||||
List<DiskSmart> diskSmart;
|
|
||||||
List<NvidiaSmiItem>? nvidia;
|
List<NvidiaSmiItem>? nvidia;
|
||||||
List<AmdSmiItem>? amd;
|
|
||||||
final List<Battery> batteries = [];
|
final List<Battery> batteries = [];
|
||||||
final Map<StatusCmdType, String> more = {};
|
final Map<StatusCmdType, String> more = {};
|
||||||
final List<SensorItem> sensors = [];
|
final List<SensorItem> sensors = [];
|
||||||
@@ -60,7 +61,6 @@ 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,
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:server_box/data/model/app/error.dart';
|
import 'package:json_annotation/json_annotation.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';
|
|
||||||
|
|
||||||
part 'server_private_info.freezed.dart';
|
import 'package:server_box/data/model/app/error.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,75 +18,79 @@ 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`.
|
||||||
@freezed
|
@JsonSerializable()
|
||||||
abstract class Spi with _$Spi {
|
@HiveType(typeId: 3)
|
||||||
const Spi._();
|
class Spi {
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
@HiveField(1)
|
||||||
|
final String ip;
|
||||||
|
@HiveField(2)
|
||||||
|
final int port;
|
||||||
|
@HiveField(3)
|
||||||
|
final String user;
|
||||||
|
@HiveField(4)
|
||||||
|
final String? pwd;
|
||||||
|
|
||||||
@JsonSerializable(includeIfNull: false)
|
/// [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 private key
|
/// [id] of the jump server
|
||||||
@JsonKey(name: 'pubKeyId') String? keyId,
|
@HiveField(9)
|
||||||
List<String>? tags,
|
final String? jumpId;
|
||||||
String? alterUrl,
|
|
||||||
@Default(true) bool autoConnect,
|
|
||||||
|
|
||||||
/// [id] of the jump server
|
@HiveField(10)
|
||||||
String? jumpId,
|
final ServerCustom? custom;
|
||||||
ServerCustom? custom,
|
|
||||||
WakeOnLanCfg? wolCfg,
|
|
||||||
|
|
||||||
/// It only applies to SSH terminal.
|
@HiveField(11)
|
||||||
Map<String, String>? envs,
|
final WakeOnLanCfg? wolCfg;
|
||||||
@Default('') @JsonKey(fromJson: Spi.parseId) String id,
|
|
||||||
}) = _Spi;
|
/// It only applies to SSH terminal.
|
||||||
|
@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);
|
||||||
|
|
||||||
@override
|
Map<String, dynamic> toJson() => _$SpiToJson(this);
|
||||||
String toString() => 'Spi<$oldId>';
|
|
||||||
|
|
||||||
static String parseId(Object? id) {
|
@override
|
||||||
if (id == null || id is! String || id.isEmpty) return ShortId.generate();
|
String toString() => id;
|
||||||
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 user != old.user ||
|
return id != old.id ||
|
||||||
ip != old.ip ||
|
|
||||||
port != old.port ||
|
|
||||||
pwd != old.pwd ||
|
pwd != old.pwd ||
|
||||||
keyId != old.keyId ||
|
keyId != old.keyId ||
|
||||||
alterUrl != old.alterUrl ||
|
alterUrl != old.alterUrl ||
|
||||||
@@ -118,27 +122,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 final example = Spi(
|
static const 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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// 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
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$Spi {
|
|
||||||
|
|
||||||
String get name; String get ip; int get port; String get user; String? get pwd;/// [id] of private key
|
|
||||||
@JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server
|
|
||||||
String? get jumpId; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal.
|
|
||||||
Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;
|
|
||||||
/// Create a copy of Spi
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SpiCopyWith<Spi> get copyWith => _$SpiCopyWithImpl<Spi>(this as Spi, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this Spi to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SpiCopyWith<$Res> {
|
|
||||||
factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SpiCopyWithImpl<$Res>
|
|
||||||
implements $SpiCopyWith<$Res> {
|
|
||||||
_$SpiCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final Spi _self;
|
|
||||||
final $Res Function(Spi) _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(_self.copyWith(
|
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,pwd: freezed == pwd ? _self.pwd : pwd // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,keyId: freezed == keyId ? _self.keyId : keyId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
|
||||||
as WakeOnLanCfg?,envs: freezed == envs ? _self.envs : envs // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
@JsonSerializable(includeIfNull: false)
|
|
||||||
class _Spi extends Spi {
|
|
||||||
const _Spi({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, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = ''}): _tags = tags,_envs = envs,super._();
|
|
||||||
factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
|
|
||||||
|
|
||||||
@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() 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) final String id;
|
|
||||||
|
|
||||||
/// Create a copy of Spi
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SpiCopyWith<_Spi> get copyWith => __$SpiCopyWithImpl<_Spi>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SpiToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SpiCopyWith<$Res> implements $SpiCopyWith<$Res> {
|
|
||||||
factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SpiCopyWithImpl<$Res>
|
|
||||||
implements _$SpiCopyWith<$Res> {
|
|
||||||
__$SpiCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _Spi _self;
|
|
||||||
final $Res Function(_Spi) _then;
|
|
||||||
|
|
||||||
/// Create a copy of Spi
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $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(_Spi(
|
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,pwd: freezed == pwd ? _self.pwd : pwd // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,keyId: freezed == keyId ? _self.keyId : keyId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
|
||||||
as WakeOnLanCfg?,envs: freezed == envs ? _self._envs : envs // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -2,46 +2,118 @@
|
|||||||
|
|
||||||
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: json['id'] == null ? '' : 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,
|
||||||
if (instance.pwd case final value?) 'pwd': value,
|
'pwd': instance.pwd,
|
||||||
if (instance.keyId case final value?) 'pubKeyId': value,
|
'pubKeyId': instance.keyId,
|
||||||
if (instance.tags case final value?) 'tags': value,
|
'tags': instance.tags,
|
||||||
if (instance.alterUrl case final value?) 'alterUrl': value,
|
'alterUrl': instance.alterUrl,
|
||||||
'autoConnect': instance.autoConnect,
|
'autoConnect': instance.autoConnect,
|
||||||
if (instance.jumpId case final value?) 'jumpId': value,
|
'jumpId': instance.jumpId,
|
||||||
if (instance.custom case final value?) 'custom': value,
|
'custom': instance.custom,
|
||||||
if (instance.wolCfg case final value?) 'wolCfg': value,
|
'wolCfg': instance.wolCfg,
|
||||||
if (instance.envs case final value?) 'envs': value,
|
'envs': instance.envs,
|
||||||
'id': instance.id,
|
};
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,18 +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/amd.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;
|
||||||
@@ -39,8 +38,7 @@ 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 =
|
final time = int.tryParse(StatusCmdType.time.find(segments)) ??
|
||||||
int.tryParse(StatusCmdType.time.find(segments)) ??
|
|
||||||
DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -51,7 +49,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final sys = _parseSysVer(StatusCmdType.sys.find(segments));
|
final sys = _parseSysVer(
|
||||||
|
StatusCmdType.sys.find(segments),
|
||||||
|
);
|
||||||
if (sys != null) {
|
if (sys != null) {
|
||||||
req.ss.more[StatusCmdType.sys] = sys;
|
req.ss.more[StatusCmdType.sys] = sys;
|
||||||
}
|
}
|
||||||
@@ -131,25 +131,12 @@ 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) {
|
||||||
Loggers.app.warning(e, s);
|
Loggers.app.warning(e, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
req.ss.amd = AmdSmi.fromJson(StatusCmdType.amd.find(segments));
|
|
||||||
} catch (e, s) {
|
|
||||||
Loggers.app.warning(e, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final battery = StatusCmdType.battery.find(segments);
|
final battery = StatusCmdType.battery.find(segments);
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,42 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:hive_flutter/hive_flutter.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';
|
|
||||||
|
|
||||||
@freezed
|
@JsonSerializable()
|
||||||
abstract class Snippet with _$Snippet {
|
@HiveType(typeId: 2)
|
||||||
const factory Snippet({
|
class Snippet {
|
||||||
required String name,
|
@HiveField(0)
|
||||||
required String script,
|
final String name;
|
||||||
List<String>? tags,
|
@HiveField(1)
|
||||||
String? note,
|
final String script;
|
||||||
|
@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
|
||||||
List<String>? autoRunOn,
|
@HiveField(4)
|
||||||
}) = _Snippet;
|
final List<String>? autoRunOn;
|
||||||
|
|
||||||
factory Snippet.fromJson(Map<String, dynamic> json) => _$SnippetFromJson(json);
|
const Snippet({
|
||||||
|
required this.name,
|
||||||
|
required this.script,
|
||||||
|
this.tags,
|
||||||
|
this.note,
|
||||||
|
this.autoRunOn,
|
||||||
|
});
|
||||||
|
|
||||||
static const example = Snippet(
|
factory Snippet.fromJson(Map<String, dynamic> json) =>
|
||||||
name: 'example',
|
_$SnippetFromJson(json);
|
||||||
script: 'echo hello',
|
|
||||||
tags: ['tag'],
|
Map<String, dynamic> toJson() => _$SnippetToJson(this);
|
||||||
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) {
|
||||||
@@ -170,6 +175,14 @@ extension SnippetX on 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 {
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// 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
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$Snippet {
|
|
||||||
|
|
||||||
String get name; String get script; List<String>? get tags; String? get note;/// List of server id that this snippet should be auto run on
|
|
||||||
List<String>? get autoRunOn;
|
|
||||||
/// Create a copy of Snippet
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnippetCopyWith<Snippet> get copyWith => _$SnippetCopyWithImpl<Snippet>(this as Snippet, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this Snippet to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Snippet&&(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));
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Snippet(name: $name, script: $script, tags: $tags, note: $note, autoRunOn: $autoRunOn)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnippetCopyWith<$Res> {
|
|
||||||
factory $SnippetCopyWith(Snippet value, $Res Function(Snippet) _then) = _$SnippetCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
String name, String script, List<String>? tags, String? note, List<String>? autoRunOn
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnippetCopyWithImpl<$Res>
|
|
||||||
implements $SnippetCopyWith<$Res> {
|
|
||||||
_$SnippetCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final Snippet _self;
|
|
||||||
final $Res Function(Snippet) _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(_self.copyWith(
|
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,script: null == script ? _self.script : script // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,note: freezed == note ? _self.note : note // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,autoRunOn: freezed == autoRunOn ? _self.autoRunOn : autoRunOn // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _Snippet implements Snippet {
|
|
||||||
const _Snippet({required this.name, required this.script, final List<String>? tags, this.note, final List<String>? autoRunOn}): _tags = tags,_autoRunOn = autoRunOn;
|
|
||||||
factory _Snippet.fromJson(Map<String, dynamic> json) => _$SnippetFromJson(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Create a copy of Snippet
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnippetCopyWith<_Snippet> get copyWith => __$SnippetCopyWithImpl<_Snippet>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnippetToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Snippet&&(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));
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Snippet(name: $name, script: $script, tags: $tags, note: $note, autoRunOn: $autoRunOn)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnippetCopyWith<$Res> implements $SnippetCopyWith<$Res> {
|
|
||||||
factory _$SnippetCopyWith(_Snippet value, $Res Function(_Snippet) _then) = __$SnippetCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
String name, String script, List<String>? tags, String? note, List<String>? autoRunOn
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnippetCopyWithImpl<$Res>
|
|
||||||
implements _$SnippetCopyWith<$Res> {
|
|
||||||
__$SnippetCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _Snippet _self;
|
|
||||||
final $Res Function(_Snippet) _then;
|
|
||||||
|
|
||||||
/// Create a copy of Snippet
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? script = null,Object? tags = freezed,Object? note = freezed,Object? autoRunOn = freezed,}) {
|
|
||||||
return _then(_Snippet(
|
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,script: null == script ? _self.script : script // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,note: freezed == note ? _self.note : note // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,autoRunOn: freezed == autoRunOn ? _self._autoRunOn : autoRunOn // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<String>?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -2,24 +2,74 @@
|
|||||||
|
|
||||||
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(
|
Snippet _$SnippetFromJson(Map<String, dynamic> json) => Snippet(
|
||||||
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(),
|
||||||
note: json['note'] as String?,
|
note: json['note'] as String?,
|
||||||
autoRunOn: (json['autoRunOn'] as List<dynamic>?)
|
autoRunOn: (json['autoRunOn'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SnippetToJson(_Snippet instance) => <String, dynamic>{
|
Map<String, dynamic> _$SnippetToJson(Snippet instance) => <String, dynamic>{
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'script': instance.script,
|
'script': instance.script,
|
||||||
'tags': instance.tags,
|
'tags': instance.tags,
|
||||||
'note': instance.note,
|
'note': instance.note,
|
||||||
'autoRunOn': instance.autoRunOn,
|
'autoRunOn': instance.autoRunOn,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
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(includeIfNull: false)
|
@JsonSerializable()
|
||||||
|
@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({
|
||||||
@@ -21,12 +26,18 @@ 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(':') ? InternetAddressType.IPv6 : InternetAddressType.IPv4,
|
type: ip.contains(':')
|
||||||
|
? InternetAddressType.IPv6
|
||||||
|
: InternetAddressType.IPv4,
|
||||||
);
|
);
|
||||||
final pwdValidation = pwd != null ? SecureONPassword.validate(pwd) : (state: true, error: null);
|
final pwdValidation = pwd != null
|
||||||
|
? SecureONPassword.validate(pwd)
|
||||||
|
: (state: true, error: null);
|
||||||
|
|
||||||
final valid = macValidation.state && ipValidation.state && pwdValidation.state;
|
final valid =
|
||||||
final err = macValidation.error ?? ipValidation.error ?? pwdValidation.error;
|
macValidation.state && ipValidation.state && pwdValidation.state;
|
||||||
|
final err =
|
||||||
|
macValidation.error ?? ipValidation.error ?? pwdValidation.error;
|
||||||
return (err, valid);
|
return (err, valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +56,8 @@ final class WakeOnLanCfg {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory WakeOnLanCfg.fromJson(Map<String, dynamic> json) => _$WakeOnLanCfgFromJson(json);
|
factory WakeOnLanCfg.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$WakeOnLanCfgFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$WakeOnLanCfgToJson(this);
|
Map<String, dynamic> toJson() => _$WakeOnLanCfgToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,63 @@
|
|||||||
|
|
||||||
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,
|
||||||
if (instance.pwd case final value?) 'pwd': value,
|
'pwd': instance.pwd,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class _AbsolutePath {
|
|||||||
_path = newPath;
|
_path = newPath;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_path = _path.joinPath(newPath, separator: _sep);
|
_path = _path.joinPath(newPath, seperator: _sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool undo() {
|
bool undo() {
|
||||||
|
|||||||
@@ -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.fetchOne(jumpSpi?.keyId)?.key;
|
jumpPrivateKey = Stores.key.get(jumpSpi?.keyId)?.key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ 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:
|
||||||
@@ -99,21 +101,16 @@ Future<void> _download(
|
|||||||
mainSendPort.send(size);
|
mainSendPort.send(size);
|
||||||
mainSendPort.send(SftpWorkerStatus.loading);
|
mainSendPort.send(SftpWorkerStatus.loading);
|
||||||
|
|
||||||
|
// Read 2m each time
|
||||||
// Issue #161
|
// Issue #161
|
||||||
// Due to single core performance, limit the chunk size
|
// The download speed is about 2m/s may due to single core performance
|
||||||
const defaultChunkSize = 1024 * 1024 * 5;
|
const defaultChunkSize = 1024 * 1024 * 2;
|
||||||
var totalRead = 0;
|
final chunkSize = size > defaultChunkSize ? defaultChunkSize : size;
|
||||||
|
for (var i = 0; i < size; i += chunkSize) {
|
||||||
while (totalRead < size) {
|
final fileData = file.read(length: chunkSize);
|
||||||
final remaining = size - totalRead;
|
await for (var form in fileData) {
|
||||||
final chunkSize = remaining > defaultChunkSize ? defaultChunkSize : remaining;
|
localFile.add(form);
|
||||||
dprint('Size: $size, Total Read: $totalRead, Chunk Size: $chunkSize');
|
mainSendPort.send((i + form.length) / size * 100);
|
||||||
|
|
||||||
final fileData = file.read(offset: totalRead, length: chunkSize);
|
|
||||||
await for (var chunk in fileData) {
|
|
||||||
localFile.add(chunk);
|
|
||||||
totalRead += chunk.length;
|
|
||||||
mainSendPort.send(totalRead / size * 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,81 @@
|
|||||||
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,
|
||||||
shift,
|
@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 {
|
||||||
@@ -106,7 +130,6 @@ extension VirtKeyX on VirtKey {
|
|||||||
VirtKey.right,
|
VirtKey.right,
|
||||||
VirtKey.clipboard,
|
VirtKey.clipboard,
|
||||||
VirtKey.ime,
|
VirtKey.ime,
|
||||||
VirtKey.shift,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Corresponding [TerminalKey]
|
/// Corresponding [TerminalKey]
|
||||||
@@ -121,21 +144,8 @@ extension VirtKeyX on VirtKey {
|
|||||||
VirtKey.left => TerminalKey.arrowLeft,
|
VirtKey.left => TerminalKey.arrowLeft,
|
||||||
VirtKey.down => TerminalKey.arrowDown,
|
VirtKey.down => TerminalKey.arrowDown,
|
||||||
VirtKey.right => TerminalKey.arrowRight,
|
VirtKey.right => TerminalKey.arrowRight,
|
||||||
VirtKey.shift => TerminalKey.shift,
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -164,7 +174,7 @@ extension VirtKeyX on VirtKey {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool get toggleable => switch (this) {
|
bool get toggleable => switch (this) {
|
||||||
VirtKey.alt || VirtKey.ctrl || VirtKey.shift => true,
|
VirtKey.alt || VirtKey.ctrl => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
196
lib/data/model/ssh/virtual_key.g.dart
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -1,27 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'app.g.dart';
|
final class AppProvider {
|
||||||
part 'app.freezed.dart';
|
const AppProvider._();
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class AppState with _$AppState {
|
|
||||||
const factory AppState({
|
|
||||||
@Default(false) bool desktopMode,
|
|
||||||
}) = _AppState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
class AppProvider extends _$AppProvider {
|
|
||||||
static BuildContext? ctx;
|
static BuildContext? ctx;
|
||||||
|
|
||||||
@override
|
|
||||||
AppState build() {
|
|
||||||
return const AppState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDesktop(bool desktopMode) {
|
|
||||||
state = state.copyWith(desktopMode: desktopMode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// 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
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$AppState {
|
|
||||||
|
|
||||||
bool get desktopMode;
|
|
||||||
/// Create a copy of AppState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(this as AppState, _$identity);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.desktopMode, desktopMode) || other.desktopMode == desktopMode));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,desktopMode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AppState(desktopMode: $desktopMode)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $AppStateCopyWith<$Res> {
|
|
||||||
factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
bool desktopMode
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$AppStateCopyWithImpl<$Res>
|
|
||||||
implements $AppStateCopyWith<$Res> {
|
|
||||||
_$AppStateCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final AppState _self;
|
|
||||||
final $Res Function(AppState) _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(_self.copyWith(
|
|
||||||
desktopMode: null == desktopMode ? _self.desktopMode : desktopMode // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
|
|
||||||
class _AppState implements AppState {
|
|
||||||
const _AppState({this.desktopMode = false});
|
|
||||||
|
|
||||||
|
|
||||||
@override@JsonKey() final bool desktopMode;
|
|
||||||
|
|
||||||
/// Create a copy of AppState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>(this, _$identity);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.desktopMode, desktopMode) || other.desktopMode == desktopMode));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,desktopMode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AppState(desktopMode: $desktopMode)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> {
|
|
||||||
factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
bool desktopMode
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$AppStateCopyWithImpl<$Res>
|
|
||||||
implements _$AppStateCopyWith<$Res> {
|
|
||||||
__$AppStateCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _AppState _self;
|
|
||||||
final $Res Function(_AppState) _then;
|
|
||||||
|
|
||||||
/// Create a copy of AppState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? desktopMode = null,}) {
|
|
||||||
return _then(_AppState(
|
|
||||||
desktopMode: null == desktopMode ? _self.desktopMode : desktopMode // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -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';
|
||||||
|
|
||||||
@@ -222,23 +222,6 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<ContainerErr?> restart(String id) async => await run('restart $id');
|
Future<ContainerErr?> restart(String id) async => await run('restart $id');
|
||||||
|
|
||||||
Future<ContainerErr?> pruneImages({bool all = true}) async {
|
|
||||||
final cmd = 'image prune${all ? " -a" : ""} -f';
|
|
||||||
return await run(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ContainerErr?> pruneContainers() async {
|
|
||||||
return await run('container prune -f');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ContainerErr?> pruneVolumes() async {
|
|
||||||
return await run('volume prune -f');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ContainerErr?> pruneSystem() async {
|
|
||||||
return await run('system prune -a -f --volumes');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ContainerErr?> run(String cmd, {bool autoRefresh = true}) async {
|
Future<ContainerErr?> run(String cmd, {bool autoRefresh = true}) async {
|
||||||
cmd = switch (type) {
|
cmd = switch (type) {
|
||||||
ContainerType.docker => 'docker $cmd',
|
ContainerType.docker => 'docker $cmd',
|
||||||
@@ -289,8 +272,6 @@ enum ContainerCmdType {
|
|||||||
ps,
|
ps,
|
||||||
stats,
|
stats,
|
||||||
images,
|
images,
|
||||||
// No specific commands needed for prune actions as they are simple
|
|
||||||
// and don't require splitting output with ShellFunc.seperator
|
|
||||||
;
|
;
|
||||||
|
|
||||||
String exec(
|
String exec(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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';
|
||||||
@@ -11,6 +10,7 @@ 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);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
// import 'dart:io';
|
// import 'dart:io';
|
||||||
|
|
||||||
import 'package:computer/computer.dart';
|
import 'package:computer/computer.dart';
|
||||||
@@ -7,17 +6,18 @@ 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,20 +45,22 @@ 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 && !originServer.value.spi.shouldReconnect(spi)) {
|
if (originServer != null &&
|
||||||
originServer.value.spi = spi;
|
!originServer.value.spi.shouldReconnect(spi)) {
|
||||||
servers[spi.id] = originServer;
|
newServer.conn = originServer.value.conn;
|
||||||
} 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(order: serverOrder_, finder: (n, id) => n.id == id);
|
spis.reorder(
|
||||||
|
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);
|
||||||
@@ -103,30 +105,31 @@ 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({Spi? spi, bool onlyFailed = false}) async {
|
static Future<void> refresh({
|
||||||
|
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(
|
await Future.wait(servers.values.map((val) async {
|
||||||
servers.values.map((val) async {
|
final s = val.value;
|
||||||
final s = val.value;
|
if (onlyFailed) {
|
||||||
if (onlyFailed) {
|
if (s.conn != ServerConn.failed) return;
|
||||||
if (s.conn != ServerConn.failed) return;
|
TryLimiter.reset(s.spi.id);
|
||||||
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 {
|
||||||
@@ -171,16 +174,12 @@ class ServerProvider extends Provider {
|
|||||||
|
|
||||||
static void _closeOneServer(String id) {
|
static void _closeOneServer(String id) {
|
||||||
final s = servers[id];
|
final s = servers[id];
|
||||||
if (s == null) {
|
final item = s?.value;
|
||||||
Loggers.app.warning('Server with id $id not found');
|
item?.client?.close();
|
||||||
return;
|
item?.client = null;
|
||||||
}
|
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) {
|
||||||
@@ -209,12 +208,14 @@ 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.clear();
|
Stores.server.deleteAll();
|
||||||
_updateTags();
|
_updateTags();
|
||||||
bakSync.sync(milliDelay: 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> updateServer(Spi old, Spi newSpi) async {
|
static Future<void> updateServer(
|
||||||
|
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;
|
||||||
@@ -235,7 +236,7 @@ class ServerProvider extends Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_updateTags();
|
_updateTags();
|
||||||
bakSync.sync(milliDelay: 1000);
|
bakSync.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _setServerState(VNode<Server> s, ServerConn ss) {
|
static void _setServerState(VNode<Server> s, ServerConn ss) {
|
||||||
@@ -305,11 +306,14 @@ class ServerProvider extends Provider {
|
|||||||
_setServerState(s, ServerConn.connected);
|
_setServerState(s, ServerConn.connected);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final (_, writeScriptResult) = await sv.client!.exec((session) async {
|
final (_, writeScriptResult) = await sv.client!.exec(
|
||||||
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
|
(session) async {
|
||||||
session.stdin.add(scriptRaw);
|
final scriptRaw = ShellFunc.allScript(spi.custom?.cmds).uint8List;
|
||||||
session.stdin.close();
|
session.stdin.add(scriptRaw);
|
||||||
}, entry: ShellFunc.getInstallShellCmd(spi.id));
|
session.stdin.close();
|
||||||
|
},
|
||||||
|
entry: ShellFunc.getInstallShellCmd(spi.id),
|
||||||
|
);
|
||||||
if (writeScriptResult.isNotEmpty) {
|
if (writeScriptResult.isNotEmpty) {
|
||||||
ShellFunc.switchScriptDir(spi.id);
|
ShellFunc.switchScriptDir(spi.id);
|
||||||
throw writeScriptResult;
|
throw writeScriptResult;
|
||||||
@@ -361,7 +365,10 @@ class ServerProvider extends Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
sv.status.err = SSHErr(type: SSHErrType.segements, message: 'Seperate segments failed, raw:\n$raw');
|
sv.status.err = SSHErr(
|
||||||
|
type: SSHErrType.segements,
|
||||||
|
message: 'Seperate segments failed, raw:\n$raw',
|
||||||
|
);
|
||||||
_setServerState(s, ServerConn.failed);
|
_setServerState(s, ServerConn.failed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -400,10 +407,17 @@ class ServerProvider extends Provider {
|
|||||||
system: systemType,
|
system: systemType,
|
||||||
customCmds: spi.custom?.cmds ?? {},
|
customCmds: spi.custom?.cmds ?? {},
|
||||||
);
|
);
|
||||||
sv.status = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${sv.id}>');
|
sv.status = await Computer.shared.start(
|
||||||
|
getStatus,
|
||||||
|
req,
|
||||||
|
taskName: 'StatusUpdateReq<${sv.id}>',
|
||||||
|
);
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
sv.status.err = SSHErr(type: SSHErrType.getStatus, message: 'Parse failed: $e\n\n$raw');
|
sv.status.err = SSHErr(
|
||||||
|
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;
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ class SftpProvider extends Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int add(SftpReq req, {Completer? completer}) {
|
static int add(SftpReq req, {Completer? completer}) {
|
||||||
final reqStat = SftpReqStatus(notifyListeners: status.notify, completer: completer, req: req);
|
final reqStat = SftpReqStatus(
|
||||||
|
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;
|
||||||
@@ -30,10 +34,6 @@ 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();
|
||||||
|
|||||||
@@ -23,15 +23,6 @@ class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shift = false;
|
|
||||||
bool get shift => _shift;
|
|
||||||
set shift(bool value) {
|
|
||||||
if (value != _shift) {
|
|
||||||
_shift = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(TerminalKeyboardEvent e) {
|
void reset(TerminalKeyboardEvent e) {
|
||||||
if (e.ctrl) {
|
if (e.ctrl) {
|
||||||
ctrl = false;
|
ctrl = false;
|
||||||
@@ -39,9 +30,6 @@ class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {
|
|||||||
if (e.alt) {
|
if (e.alt) {
|
||||||
alt = false;
|
alt = false;
|
||||||
}
|
}
|
||||||
if (e.shift) {
|
|
||||||
shift = false;
|
|
||||||
}
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +38,6 @@ class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {
|
|||||||
final e = event.copyWith(
|
final e = event.copyWith(
|
||||||
ctrl: event.ctrl || ctrl,
|
ctrl: event.ctrl || ctrl,
|
||||||
alt: event.alt || alt,
|
alt: event.alt || alt,
|
||||||
shift: event.shift || shift,
|
|
||||||
);
|
);
|
||||||
if (Stores.setting.sshVirtualKeyAutoOff.fetch()) {
|
if (Stores.setting.sshVirtualKeyAutoOff.fetch()) {
|
||||||
reset(e);
|
reset(e);
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
abstract class BuildData {
|
abstract class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 1201;
|
static const int build = 1104;
|
||||||
static const int script = 65;
|
static const int script = 58;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ abstract final class GithubIds {
|
|||||||
'dccif',
|
'dccif',
|
||||||
'mikropsoft',
|
'mikropsoft',
|
||||||
'CakesTwix',
|
'CakesTwix',
|
||||||
'dsvf',
|
|
||||||
'fei1025',
|
|
||||||
'MasedMSD',
|
|
||||||
'GitGitro',
|
|
||||||
'Shin-suechtig',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const participants = <GhId>{
|
static const participants = <GhId>{
|
||||||
@@ -104,21 +99,6 @@ 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',
|
|
||||||
'back-lacking',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
lib/data/res/rebuild.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
|
abstract final class RNodes {
|
||||||
|
static final app = RNode();
|
||||||
|
static final dark = false.vn;
|
||||||
|
}
|
||||||
@@ -1,40 +1,64 @@
|
|||||||
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/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/server.dart';
|
import 'package:server_box/data/model/server/conn.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 =>
|
static SingleCpuCore get _initOneTimeCpuStatus => SingleCpuCore(
|
||||||
SingleCpuCore('cpu', 0, 0, 0, 0, 0, 0, 0);
|
'cpu',
|
||||||
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,
|
||||||
|
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(total: 1, free: 1, avail: 1),
|
mem: const Memory(
|
||||||
disk: [
|
total: 1,
|
||||||
Disk(
|
free: 1,
|
||||||
path: '/',
|
avail: 1,
|
||||||
mount: '/',
|
),
|
||||||
usedPercent: 0,
|
disk: [
|
||||||
used: BigInt.zero,
|
Disk(
|
||||||
size: BigInt.one,
|
fs: '/',
|
||||||
avail: BigInt.zero,
|
mount: '/',
|
||||||
),
|
usedPercent: 0,
|
||||||
],
|
used: BigInt.zero,
|
||||||
tcp: const Conn(maxConn: 0, active: 0, passive: 0, fail: 0),
|
size: BigInt.one,
|
||||||
netSpeed: netSpeed,
|
avail: BigInt.zero,
|
||||||
swap: const Swap(total: 0, free: 0, cached: 0),
|
)
|
||||||
system: SystemType.linux,
|
],
|
||||||
temps: Temperatures(),
|
tcp: const Conn(maxConn: 0, active: 0, passive: 0, fail: 0),
|
||||||
diskIO: DiskIO([], []),
|
netSpeed: netSpeed,
|
||||||
diskSmart: const [],
|
swap: const Swap(
|
||||||
);
|
total: 0,
|
||||||
|
free: 0,
|
||||||
|
cached: 0,
|
||||||
|
),
|
||||||
|
system: SystemType.linux,
|
||||||
|
temps: Temperatures(),
|
||||||
|
diskIO: DiskIO([], []),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
@@ -15,7 +16,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<HiveStore> _allBackup = [
|
static final List<PersistentStore> _allBackup = [
|
||||||
SettingStore.instance,
|
SettingStore.instance,
|
||||||
ServerStore.instance,
|
ServerStore.instance,
|
||||||
ContainerStore.instance,
|
ContainerStore.instance,
|
||||||
@@ -26,24 +27,15 @@ 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 {
|
||||||
var lastModTime = 0;
|
int? lastModTime = 0;
|
||||||
for (final store in _allBackup) {
|
for (final store in _allBackup) {
|
||||||
final last = store.lastUpdateTs;
|
final last = store.box.lastModified ?? 0;
|
||||||
if (last == null) {
|
if (last > (lastModTime ?? 0)) {
|
||||||
continue;
|
lastModTime = last;
|
||||||
}
|
|
||||||
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;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:server_box/data/res/store.dart';
|
|||||||
|
|
||||||
const _keyConfig = 'providerConfig';
|
const _keyConfig = 'providerConfig';
|
||||||
|
|
||||||
class ContainerStore extends HiveStore {
|
class ContainerStore extends PersistentStore {
|
||||||
ContainerStore._() : super('docker');
|
ContainerStore._() : super('docker');
|
||||||
|
|
||||||
static final instance = ContainerStore._();
|
static final instance = ContainerStore._();
|
||||||
@@ -14,7 +14,8 @@ class ContainerStore extends HiveStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void put(String id, String host) {
|
void put(String id, String host) {
|
||||||
set(id, host);
|
box.put(id, host);
|
||||||
|
box.updateLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
ContainerType getType([String id = '']) {
|
ContainerType getType([String id = '']) {
|
||||||
@@ -29,17 +30,16 @@ class ContainerStore extends HiveStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContainerType get defaultType {
|
ContainerType get defaultType {
|
||||||
if (Stores.setting.usePodman.get()) return ContainerType.podman;
|
if (Stores.setting.usePodman.fetch()) 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
|
||||||
/// index from 0 -> n : latest -> oldest
|
/// index from 0 -> n : latest -> oldest
|
||||||
class _ListHistory {
|
class _ListHistory {
|
||||||
@@ -18,6 +18,7 @@ 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;
|
||||||
@@ -38,12 +39,13 @@ 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 HiveStore {
|
class HistoryStore extends PersistentStore {
|
||||||
HistoryStore._() : super('history');
|
HistoryStore._() : super('history');
|
||||||
|
|
||||||
static final instance = HistoryStore._();
|
static final instance = HistoryStore._();
|
||||||
@@ -56,6 +58,5 @@ class HistoryStore extends HiveStore {
|
|||||||
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 =
|
late final writeScriptTipShown = property('writeScriptTipShown', false);
|
||||||
propertyDefault('writeScriptTipShown', false);
|
|
||||||
}
|
}
|
||||||
|
|||||||
49
lib/data/store/no_backup.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,49 +2,35 @@ 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 HiveStore {
|
class PrivateKeyStore extends PersistentStore {
|
||||||
PrivateKeyStore._() : super('key');
|
PrivateKeyStore._() : super('key');
|
||||||
|
|
||||||
static final instance = PrivateKeyStore._();
|
static final instance = PrivateKeyStore._();
|
||||||
|
|
||||||
void put(PrivateKeyInfo info) {
|
void put(PrivateKeyInfo info) {
|
||||||
set(info.id, info);
|
box.put(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 = get<PrivateKeyInfo>(
|
final s = box.get(key);
|
||||||
key,
|
if (s != null && s is PrivateKeyInfo) {
|
||||||
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? fetchOne(String? id) {
|
PrivateKeyInfo? get(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) {
|
||||||
remove(s.id);
|
box.delete(s.id);
|
||||||
|
box.updateLastModified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||