Compare commits
189 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b56e033773 | ||
|
|
7dda63af8a | ||
|
|
00d303ac36 | ||
|
|
229983d82e | ||
|
|
4928ca600d | ||
|
|
89ec2d94d6 | ||
|
|
393c3e6388 | ||
|
|
dee458e926 | ||
|
|
f89228db40 | ||
|
|
3b6fb6194b | ||
|
|
02444fc2f0 | ||
|
|
aef317a140 | ||
|
|
47aedb2f2e | ||
|
|
eab06abcaf | ||
|
|
c062c12a0e | ||
|
|
d7669c94b8 | ||
|
|
50af289574 | ||
|
|
90b88ed795 | ||
|
|
d611fdcd50 | ||
|
|
db9b2dd818 | ||
|
|
edb49ead67 | ||
|
|
7f0dc656b8 | ||
|
|
b33d0bbc3e | ||
|
|
7d0ea8a58b | ||
|
|
c18732d8f3 | ||
|
|
157af0a354 | ||
|
|
2d9dc044f9 | ||
|
|
479250c207 | ||
|
|
aef7ec911f | ||
|
|
4f9ee7781f | ||
|
|
eb83d05c81 | ||
|
|
329fd33b69 | ||
|
|
931c5f0bf6 | ||
|
|
bcbf1fbc17 | ||
|
|
3e7315dac6 | ||
|
|
4cecfdf7a8 | ||
|
|
0346821cf5 | ||
|
|
966a60a82d | ||
|
|
76e98c6468 | ||
|
|
d7ae8b75b8 | ||
|
|
b5329e2692 | ||
|
|
ef297673f3 | ||
|
|
7558b4806d | ||
|
|
f7ef8a3915 | ||
|
|
38366a2ef3 | ||
|
|
7e5bb54c98 | ||
|
|
7ce3854392 | ||
|
|
195ddd2bcc | ||
|
|
267b0b0a69 | ||
|
|
41e3fcb23a | ||
|
|
46d5840276 | ||
|
|
fe566e97ca | ||
|
|
ddd1524d63 | ||
|
|
4d8268c614 | ||
|
|
568b97606a | ||
|
|
42cc2416a1 | ||
|
|
aaa69f0f95 | ||
|
|
64676bc5cb | ||
|
|
a15c04956c | ||
|
|
e3c885483b | ||
|
|
493c86cacb | ||
|
|
ea7c8caf14 | ||
|
|
9db04a60c2 | ||
|
|
610f46da0d | ||
|
|
b8e5418ff2 | ||
|
|
0e21755acb | ||
|
|
73248011a1 | ||
|
|
969643d3df | ||
|
|
c90d0e4b3b | ||
|
|
f9aadc6b0f | ||
|
|
8fd4cc1fe1 | ||
|
|
432d76f024 | ||
|
|
ca8211e1a4 | ||
|
|
a3b48fc01c | ||
|
|
8be94aa09c | ||
|
|
5db1253ab8 | ||
|
|
ceedd86310 | ||
|
|
6a0254623f | ||
|
|
1c6ec56032 | ||
|
|
287869ed45 | ||
|
|
e4dbc3ba12 | ||
|
|
426e5689f8 | ||
|
|
afda5fd4a4 | ||
|
|
0a21b2820c | ||
|
|
87b3b76b0b | ||
|
|
41ec46f1d3 | ||
|
|
7a359588db | ||
|
|
255abe8b11 | ||
|
|
b0936c5e6e | ||
|
|
2907ac74d4 | ||
|
|
ea678f37b0 | ||
|
|
076082c945 | ||
|
|
5ee98f90e8 | ||
|
|
c988dd88d7 | ||
|
|
f7d6c461dc | ||
|
|
14771ae946 | ||
|
|
7e9086b20e | ||
|
|
4b3c4870ba | ||
|
|
43cebd0c04 | ||
|
|
5ce13109b0 | ||
|
|
6e428c91d1 | ||
|
|
4430045550 | ||
|
|
282cb06091 | ||
|
|
772c2743b5 | ||
|
|
90199b89a5 | ||
|
|
e1d2e3f3e5 | ||
|
|
3f9fe1f2c6 | ||
|
|
c79bbc5756 | ||
|
|
63e1bec2b9 | ||
|
|
d26b7c6f75 | ||
|
|
da8dc4fa54 | ||
|
|
413b81c47f | ||
|
|
9ef419e3df | ||
|
|
39893912d9 | ||
|
|
49456ca6c3 | ||
|
|
6b9b8f0dbb | ||
|
|
5eb48b2717 | ||
|
|
5339cfca70 | ||
|
|
1462b2d0b8 | ||
|
|
3798a23183 | ||
|
|
ddaf916170 | ||
|
|
d6e37b058f | ||
|
|
2e9ad7d7cb | ||
|
|
190da74f66 | ||
|
|
f1315dda7f | ||
|
|
43e6105eb3 | ||
|
|
d785209eb6 | ||
|
|
da8b6a9010 | ||
|
|
1fd68722da | ||
|
|
c9a2c1d0e4 | ||
|
|
161f536a62 | ||
|
|
1a32e9944e | ||
|
|
6deb753198 | ||
|
|
4e33a98631 | ||
|
|
dcb9464d8f | ||
|
|
b94936b29f | ||
|
|
108d0a5a5b | ||
|
|
4814a2de28 | ||
|
|
5d8eeff502 | ||
|
|
0bc4087266 | ||
|
|
921209b901 | ||
|
|
fa9d754470 | ||
|
|
1f50a1f0f4 | ||
|
|
80e84c0421 | ||
|
|
5059872c3f | ||
|
|
8add244776 | ||
|
|
04e23fd7e4 | ||
|
|
336b11b808 | ||
|
|
8d9dba361c | ||
|
|
64ce3638cb | ||
|
|
f3ceb73f0e | ||
|
|
131dbe934c | ||
|
|
58288e89bd | ||
|
|
22c43c7124 | ||
|
|
2bf0b25ee5 | ||
|
|
3bc03c1364 | ||
|
|
490d71f8c9 | ||
|
|
edceb5900e | ||
|
|
e5ef28415b | ||
|
|
46b98df153 | ||
|
|
9f34021c90 | ||
|
|
8121eef839 | ||
|
|
da48d1f66c | ||
|
|
b167287c5b | ||
|
|
41f9da6bf8 | ||
|
|
e7c7fc8186 | ||
|
|
b950dd2d68 | ||
|
|
6d34de14d3 | ||
|
|
a5a84c0cdd | ||
|
|
701b1b811f | ||
|
|
97267cdfbf | ||
|
|
40ce37d230 | ||
|
|
8a9ade355c | ||
|
|
9bffec64b5 | ||
|
|
a03ee2ae0e | ||
|
|
ee889235fe | ||
|
|
94d6d80497 | ||
|
|
413c45a559 | ||
|
|
6dc5536c48 | ||
|
|
76c4bf56fa | ||
|
|
a0c6642230 | ||
|
|
4198d7bd13 | ||
|
|
b06fddec07 | ||
|
|
d1f14bee59 | ||
|
|
8953f63197 | ||
|
|
193d80d826 | ||
|
|
9e308792aa | ||
|
|
fbabd8c351 | ||
|
|
1a3cb09ca2 |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://cdn.lpkt.cn/donate']
|
||||||
91
.github/workflows/release.yml
vendored
@@ -9,44 +9,78 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
releaseAL:
|
releaseAndroid:
|
||||||
name: Release android and linux
|
name: Release android
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
flutter-version: '3.24.1'
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
- name: Fetch secrets
|
||||||
|
run: |
|
||||||
|
curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key
|
||||||
|
curl -u ${{ secrets.BASIC_AUTH }} -o android/key.properties ${{ secrets.URL_PREFIX }}key.properties
|
||||||
|
- name: Build
|
||||||
|
run: dart run fl_build -p android
|
||||||
|
- name: Rename for fdroid
|
||||||
|
run: |
|
||||||
|
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_arm64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm64.apk
|
||||||
|
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_arm.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm.apk
|
||||||
|
mv build/app/outputs/flutter-apk/${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.apk build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_amd64.apk
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm64.apk
|
||||||
|
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_arm.apk
|
||||||
|
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_v1.0.${{ env.BUILD_NUMBER }}_amd64.apk
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
releaseLinux:
|
||||||
|
name: Release linux
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
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: Build
|
- name: Build
|
||||||
run: dart run fl_build -p android,linux
|
run: |
|
||||||
|
dart run fl_build -p linux
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm64.apk
|
${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_amd64.AppImage
|
||||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_arm.apk
|
|
||||||
build/app/outputs/flutter-apk/${{ env.APP_NAME }}_amd64.apk
|
|
||||||
${{ env.APP_NAME }}_amd64.AppImage
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# releaseWin:
|
releaseWin:
|
||||||
# name: Release windows
|
name: Release windows
|
||||||
# runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout
|
- name: Checkout
|
||||||
# uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
# - name: Install Flutter
|
- name: Install Flutter
|
||||||
# uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
# - name: Build
|
- name: Build
|
||||||
# run: dart run fl_build -p windows
|
run: dart run fl_build -p windows
|
||||||
# - name: Create Release
|
- name: Create Release
|
||||||
# uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
# with:
|
with:
|
||||||
# files: |
|
files: |
|
||||||
# ${{ env.APP_NAME }}_amd64_windows.zip
|
${{ env.APP_NAME }}_${{ env.BUILD_NUMBER }}_windows_amd64.zip
|
||||||
# env:
|
env:
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# releaseApple:
|
# releaseApple:
|
||||||
# name: Release ios and macos
|
# name: Release ios and macos
|
||||||
@@ -56,10 +90,13 @@ jobs:
|
|||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v4
|
||||||
# - name: Install Flutter
|
# - name: Install Flutter
|
||||||
# uses: subosito/flutter-action@v2
|
# uses: subosito/flutter-action@v2
|
||||||
|
# with:
|
||||||
|
# channel: 'stable'
|
||||||
|
# flutter-version: '3.22.2'
|
||||||
# - name: Build
|
# - name: Build
|
||||||
# run: dart run fl_build -p ios,mac
|
# run: dart run fl_build -p ios,mac
|
||||||
# - name: Create Release
|
# - name: Create Release
|
||||||
# uses: softprops/action-gh-release@v1
|
# uses: softprops/action-gh-release@v2
|
||||||
# with:
|
# with:
|
||||||
# files: |
|
# files: |
|
||||||
# ${{ env.APP_NAME }}_universal_macos.zip
|
# ${{ env.APP_NAME }}_universal_macos.zip
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -57,9 +57,10 @@ test.dart
|
|||||||
|
|
||||||
# Linux release
|
# Linux release
|
||||||
linux.AppDir
|
linux.AppDir
|
||||||
ServerBox-x86_64.AppImage
|
**/*.AppImage
|
||||||
|
|
||||||
untranlated.json
|
untranlated.json
|
||||||
|
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
more_build_data.json
|
more_build_data.json
|
||||||
|
trans.txt
|
||||||
|
|||||||
30
.metadata
@@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "bae5e49bc2a867403c43b2aae2de8f8c33b037e4"
|
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@@ -13,26 +13,26 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: ios
|
- platform: ios
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: linux
|
- platform: linux
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: web
|
- platform: web
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
4
.vscode/launch.json
vendored
@@ -8,6 +8,10 @@
|
|||||||
"name": "debug",
|
"name": "debug",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
"env": {
|
||||||
|
// Comment this line to use the default display
|
||||||
|
"DISPLAY": ":1"
|
||||||
|
}
|
||||||
// "args": [
|
// "args": [
|
||||||
// "-v"
|
// "-v"
|
||||||
// ]
|
// ]
|
||||||
|
|||||||
95
README.md
@@ -2,11 +2,11 @@ English | [简体中文](README_zh.md)
|
|||||||
|
|
||||||
<h2 align="center">Flutter Server Box</h2>
|
<h2 align="center">Flutter Server Box</h2>
|
||||||
|
|
||||||
<p align="center">
|
<div align="center">
|
||||||
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
|
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a>
|
||||||
<img alt="countly" src="https://img.shields.io/badge/analysis-countly-pink">
|
<img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan">
|
||||||
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
|
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-yellow">
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
A Flutter project which provide charts to display <a href="../../issues/43">Linux</a> server status and tools to manage server.
|
A Flutter project which provide charts to display <a href="../../issues/43">Linux</a> server status and tools to manage server.
|
||||||
@@ -14,50 +14,42 @@ A Flutter project which provide charts to display <a href="../../issues/43">Linu
|
|||||||
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
|
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## ⬇️ Download
|
|
||||||
[iOS](https://apps.apple.com/app/id1586449703) / [Android](https://cdn.lolli.tech/serverbox/latest.apk) / [macOS](https://apps.apple.com/app/id1586449703): Full support with my own certificate
|
## 🏙️ Screenshots
|
||||||
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): Basically tested, with debug certificate
|
<table>
|
||||||
|
<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/2.jpg"></td>
|
||||||
|
<td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/3.jpg"></td>
|
||||||
|
<td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/4.jpg"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
## 📥 Install
|
||||||
|
|
||||||
|
Platform | From
|
||||||
|
--- | ---
|
||||||
|
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
|
||||||
|
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||||
|
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||||
|
|
||||||
|
Please only download pkgs from the source that **you trust**!
|
||||||
|
|
||||||
|
|
||||||
## 🔖 Feature
|
## 🔖 Feature
|
||||||
- Status chart, `SSH` Terminal, `SFTP`, `Docker & Pkg & Process`, Code editor...
|
- `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`...
|
||||||
- Localization
|
- English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic), Türkçe [@mikropsoft](https://github.com/mikropsoft), Українська мова [@CakesTwix](https://github.com/CakesTwix); Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||||
- English, 简体中文
|
|
||||||
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
|
||||||
- Deutsch (@its-tom) / 繁體中文 (@kalashnikov) / Indonesian (@azkadev) / Français (@FrancXPT) / Dutch (@QazCetelic)
|
|
||||||
|
|
||||||
|
|
||||||
## 🏙️ ScreenShots
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/server.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/detail.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/sftp.png">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/editor.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/ssh.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/docker.png">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
## 🆘 Help
|
## 🆘 Help
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
- In order to push server status to your portable device without opening ServerBox app (Such as **message push** and **home widget**), you need to install [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor) on your servers, and config it correctly. See [wiki](https://github.com/lollipopkit/server_box_monitor/wiki) for more details.
|
- In order to push server status to your portable device without opening ServerBox app (Such as **message push** and **home widget**), you need to install [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor) on your servers, and config it correctly. See [wiki](https://github.com/lollipopkit/server_box_monitor/wiki) for more details.
|
||||||
- **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).
|
||||||
|
|
||||||
@@ -66,25 +58,24 @@ Before you open an issue, please read the following:
|
|||||||
2. Make sure whether the issue is caused by ServerBox app.
|
2. Make sure whether the issue is caused by ServerBox app.
|
||||||
3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted.
|
3. Welcome all valid and positive feedback, subjective feedback (such as you think other UI is better) may not be accepted.
|
||||||
|
|
||||||
After you read the above, you can:
|
After you read the above, you can open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
|
||||||
- If you have **any question or feature request**, please open a [discussion](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose).
|
|
||||||
- If ServerBox app has **any bug**, please open an [issue](https://github.com/lollipopkit/flutter_server_box/issues/new).
|
|
||||||
|
|
||||||
|
|
||||||
## 🧱 Contribution
|
## 🧱 Contribution
|
||||||
- Any positive contribution is welcome.
|
Any positive contribution is welcome.
|
||||||
- [l10n guide](https://blog.lolli.tech/faq/) can be found in my blog.
|
|
||||||
|
|
||||||
|
### Development
|
||||||
|
1. Setup [Flutter](https://flutter.dev/docs/get-started/install) environment.
|
||||||
|
2. Clone this repo, run `flutter run` to start the app.
|
||||||
|
3. Run `dart run fl_build -p PLATFORM` to build the app.
|
||||||
|
|
||||||
## 👏🏼 Contributors
|
### Translation
|
||||||
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
|
- [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog.
|
||||||
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
|
- We need your help! Just feel free to open a PR.
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
## 💡 My other apps
|
## 💡 My other apps
|
||||||
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms.
|
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - A third-party GPT Client for OpenAI API on all platforms.
|
||||||
- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - Open source 2FA app for Android, iOS and the web.
|
|
||||||
- [More](https://github.com/lollipopkit) - Tools & etc.
|
- [More](https://github.com/lollipopkit) - Tools & etc.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
96
README_zh.md
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
<h2 align="center">Flutter Server Box</h2>
|
<h2 align="center">Flutter Server Box</h2>
|
||||||
|
|
||||||
<p align="center">
|
<div align="center">
|
||||||
<img alt="lang" src="https://img.shields.io/badge/lang-dart-pink">
|
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a>
|
||||||
<img alt="countly" src="https://img.shields.io/badge/analysis-countly-pink">
|
<img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan">
|
||||||
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-pink">
|
<img alt="license" src="https://img.shields.io/badge/证书-GPLv3-yellow">
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
使用 Flutter 开发的 <a href="../../issues/43">Linux</a> 服务器工具箱,提供服务器状态图表和管理工具。
|
使用 Flutter 开发的 <a href="../../issues/43">Linux</a> 服务器工具箱,提供服务器状态图表和管理工具。
|
||||||
@@ -15,79 +15,67 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
## ⬇️ Download
|
## 🏙️ 截屏
|
||||||
[iOS](https://apps.apple.com/app/id1586449703) / [Android](https://cdn.lolli.tech/serverbox/latest.apk) / [macOS](https://apps.apple.com/app/id1586449703): 经过测试,使用自签名证书
|
<table>
|
||||||
[Linux](https://cdn.lolli.tech/serverbox/latest.AppImage) / [Windows](https://cdn.lolli.tech/serverbox/latest.win.zip): 经过不完全测试,使用调试证书
|
<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/2.jpg"></td>
|
||||||
|
<td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/3.jpg"></td>
|
||||||
|
<td><img width="200px" src="https://cdn.lpkt.cn/serverbox/screenshot/4.jpg"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
## 📥 安装
|
||||||
|
|
||||||
|
平台 | 下载
|
||||||
|
--- | ---
|
||||||
|
iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703)
|
||||||
|
Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)
|
||||||
|
Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lolli.tech/serverbox/?sort=time&order=desc&layout=grid)
|
||||||
|
|
||||||
|
请从 **信任** 的来源下载!
|
||||||
|
|
||||||
|
|
||||||
## 🔖 特点
|
## 🔖 特点
|
||||||
- 状态图表, `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器, 代码编辑器...
|
- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程 & Systemd` 管理...
|
||||||
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
- 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`...
|
||||||
- 本地化
|
- 本地化
|
||||||
- English, 简体中文
|
- English, 简体中文
|
||||||
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
- Español, Русский язык, Português, 日本語 (Generated by GPT)
|
||||||
- Deutsch (@its-tom) / 繁體中文 (@kalashnikov) / Indonesian (@azkadev) / Français (@FrancXPT) / Dutch (@QazCetelic)
|
- 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);
|
||||||
|
- 感谢贡献者们!
|
||||||
|
|
||||||
## 🏙️ 截屏
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/server.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/detail.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/sftp.png">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/editor.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/ssh.png">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<img width="277px" src="imgs/docker.png">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
## 🆘 帮助
|
## 🆘 帮助
|
||||||
|
|
||||||
- 吹水、参与开发、了解如何使用,QQ群 **762870488**
|
<div align="center">
|
||||||
- 为了可以在不使用 ServerBox app 时获取服务器状态(例如:桌面小部件、推送服务),你需要在你的服务器上安装 [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor),并且正确配置,详情可见 [wiki](https://github.com/lollipopkit/server_box_monitor/wiki/%E4%B8%BB%E9%A1%B5)。
|
<a href="https://t.me/lpktg"><img alt="donate" src="https://img.shields.io/badge/Telegram-lpktg-green"></a>
|
||||||
- **常见问题**可以在 [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki/主页) 查看。
|
<a href="https://discord.gg/SsVNbRhK7w"><img alt="discord" src="https://img.shields.io/badge/Discord-lpkt-purple"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
- 为了可以在不使用 ServerBox app 时获取服务器状态(例如:桌面小部件、推送服务),你需要在你的服务器上安装 [ServerBoxMonitor](https://github.com/lollipopkit/server_box_monitor),详情见 [wiki](https://github.com/lollipopkit/server_box_monitor/wiki/%E4%B8%BB%E9%A1%B5)。
|
||||||
|
- **常见问题** 可以在 [app wiki](https://github.com/lollipopkit/flutter_server_box/wiki/主页) 查看。
|
||||||
|
|
||||||
反馈前须知:
|
反馈前须知:
|
||||||
1. 反馈问题请附带 log(点击首页右上角),并以 bug 模版提交。
|
1. 反馈问题请附带 log(点击首页右上角),并以 bug 模版提交。
|
||||||
2. 反馈问题前请检查是否是 serverbox 的问题。
|
2. 反馈问题前请检查是否是 serverbox 的问题。
|
||||||
3. 欢迎所有有效、正面的反馈,主观(比如你觉得其他UI更好看)的反馈不一定会接受
|
3. 欢迎所有有效、正面的反馈,主观(比如你觉得其他UI更好看)的反馈不一定会接受
|
||||||
|
|
||||||
确认了解上述内容后:
|
|
||||||
- 如果你有**任何问题或者功能请求**,请在 [讨论](https://github.com/lollipopkit/flutter_server_box/discussions/new/choose) 中交流。
|
|
||||||
- 如果 ServerBox app 有**任何 bug**,请在 [问题](https://github.com/lollipopkit/flutter_server_box/issues/new) 中反馈。
|
|
||||||
|
|
||||||
|
|
||||||
## 🧱 贡献
|
## 🧱 贡献
|
||||||
- 任何正面的贡献都欢迎。
|
任何正面的贡献都欢迎。
|
||||||
- [本地化翻译指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
|
||||||
|
|
||||||
|
### 开发
|
||||||
|
1. 安装 [Flutter](https://flutter.dev/docs/get-started/install)
|
||||||
|
2. 克隆这个仓库, 运行 `flutter run` 启动应用
|
||||||
|
3. 运行 `dart run fl_build -p PLATFORM` 构建应用
|
||||||
|
|
||||||
## 👏🏼 贡献者
|
### 翻译
|
||||||
<a href="https://github.com/lollipopkit/flutter_server_box/graphs/contributors">
|
[指南](https://blog.lolli.tech/faq/) 可在我的博客中找到。
|
||||||
<img src="https://contrib.rocks/image?repo=lollipopkit/flutter_server_box" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
## 💡 我的其它 Apps
|
## 💡 我的其它 Apps
|
||||||
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。
|
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box) - 支持 OpenAI API 的 第三方全平台客户端。
|
||||||
- [2FA Box](https://github.com/lollipopkit/flutter_2fa) - 开源的 2FA 应用。
|
|
||||||
- [更多](https://github.com/lollipopkit) - 工具 & etc.
|
- [更多](https://github.com/lollipopkit) - 工具 & etc.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,14 +30,19 @@ linter:
|
|||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
library_private_types_in_public_api: false
|
library_private_types_in_public_api: true
|
||||||
use_build_context_synchronously: false
|
use_build_context_synchronously: false
|
||||||
depend_on_referenced_packages: false
|
depend_on_referenced_packages: false
|
||||||
prefer_final_locals: true
|
prefer_final_locals: true
|
||||||
unnecessary_parenthesis: true
|
unnecessary_parenthesis: true
|
||||||
implicit_call_tearoffs: true
|
implicit_call_tearoffs: true
|
||||||
|
always_declare_return_types: true
|
||||||
|
always_use_package_imports: true
|
||||||
|
annotate_overrides: 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
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "tech.lolli.toolbox"
|
applicationId "tech.lolli.toolbox"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
@@ -100,3 +99,14 @@ flutter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {}
|
dependencies {}
|
||||||
|
|
||||||
|
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
|
||||||
|
import com.android.build.OutputFile
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
variant.outputs.each { output ->
|
||||||
|
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
|
||||||
|
if (abiVersionCode != null) {
|
||||||
|
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
android/app/proguard-rules.pro
vendored
@@ -1,7 +1 @@
|
|||||||
-keep class io.flutter.app.** { *; }
|
|
||||||
-keep class io.flutter.plugin.** { *; }
|
|
||||||
-keep class io.flutter.util.** { *; }
|
|
||||||
-keep class io.flutter.view.** { *; }
|
|
||||||
-keep class io.flutter.** { *; }
|
|
||||||
-keep class io.flutter.plugins.** { *; }
|
|
||||||
-keep class com.jcraft.** { *; }
|
-keep class com.jcraft.** { *; }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
@@ -15,7 +16,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="true"
|
||||||
android:restoreAnyVersion="true">
|
android:restoreAnyVersion="true"
|
||||||
|
tools:targetApi="q">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -29,12 +31,12 @@
|
|||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
@@ -43,11 +45,6 @@
|
|||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="id.flutter.flutter_background_service.BackgroundService"
|
|
||||||
android:foregroundServiceType="dataSync"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widget.HomeWidget"
|
android:name=".widget.HomeWidget"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -67,7 +64,12 @@
|
|||||||
android:resource="@xml/home_widget" />
|
android:resource="@xml/home_widget" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".KeepAliveService"/>
|
<service
|
||||||
|
android:name=".ForegroundService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility?hl=en and
|
https://developer.android.com/training/package-visibility?hl=en and
|
||||||
@@ -76,8 +78,8 @@
|
|||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package tech.lolli.toolbox
|
||||||
|
|
||||||
|
import android.app.*
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
|
||||||
|
class ForegroundService : Service() {
|
||||||
|
private val chanId = "ForegroundServiceChannel"
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
when (intent?.action) {
|
||||||
|
"ACTION_STOP_FOREGROUND" -> {
|
||||||
|
stopForegroundService()
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val notification = createNotification()
|
||||||
|
startForeground(1, notification)
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
chanId,
|
||||||
|
chanId,
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotification(): Notification {
|
||||||
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val deleteIntent = Intent(this, ForegroundService::class.java).apply {
|
||||||
|
action = "ACTION_STOP_FOREGROUND"
|
||||||
|
}
|
||||||
|
val deletePendingIntent = PendingIntent.getService(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
deleteIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Notification.Builder(this, chanId)
|
||||||
|
.setContentTitle("Server Box")
|
||||||
|
.setContentText("Open the app")
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
Notification.Builder(this)
|
||||||
|
.setContentTitle("Server Box")
|
||||||
|
.setContentText("Open the app")
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopForegroundService() {
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package tech.lolli.toolbox
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
import android.os.IBinder
|
|
||||||
import org.jetbrains.annotations.Nullable
|
|
||||||
|
|
||||||
class KeepAliveService : Service() {
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
return START_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
package tech.lolli.toolbox
|
package tech.lolli.toolbox
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
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
|
||||||
@@ -18,8 +23,18 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
"startService" -> {
|
"startService" -> {
|
||||||
val intent = Intent(this@MainActivity, KeepAliveService::class.java)
|
reqPerm()
|
||||||
startService(intent)
|
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(serviceIntent)
|
||||||
|
} else {
|
||||||
|
startService(serviceIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"stopService" -> {
|
||||||
|
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
||||||
|
stopService(serviceIntent)
|
||||||
|
result.success(null)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
@@ -28,4 +43,16 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun reqPerm() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
123,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ class HomeWidget : AppWidgetProvider() {
|
|||||||
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)
|
||||||
var url = sp.getString("$appWidgetId", null)
|
var url = sp.getString("widget_$appWidgetId", null)
|
||||||
val gUrl = sp.getString("*", null)
|
|
||||||
if (url.isNullOrEmpty()) {
|
if (url.isNullOrEmpty()) {
|
||||||
|
url = sp.getString("$appWidgetId", null)
|
||||||
|
}
|
||||||
|
if (url.isNullOrEmpty()) {
|
||||||
|
val gUrl = sp.getString("widget_*", null)
|
||||||
url = gUrl
|
url = gUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
@@ -3,3 +3,4 @@ distributionPath=wrapper/dists
|
|||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||||
|
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
|
||||||
|
|||||||
6
fastlane/metadata/android/en-US/full_description.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
A Flutter project which provide charts to display Linux server status and tools to manage server.
|
||||||
|
Especially thanks to dartssh2 & xterm.dart.
|
||||||
|
|
||||||
|
* Status chart (CPU, Sensors, GPU...), SSH Term, SFTP, Docker & Pkg & Process...
|
||||||
|
* Platform specific: Bio auth、Msg push、Home widget、watchOS App...
|
||||||
|
* English, 简体中文; Deutsch, 繁體中文, Indonesian, Français, Dutch; Español, Русский язык, Português, 日本語
|
||||||
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
A server status & toolbox app using Flutter
|
||||||
1
fastlane/metadata/android/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ServerBox
|
||||||
7
fastlane/metadata/android/zh-CN/full_description.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
使用 Flutter 开发的 Linux 服务器工具箱,提供服务器状态图表和管理工具。
|
||||||
|
特别感谢 dartssh2 & xterm.dart。
|
||||||
|
|
||||||
|
特点:
|
||||||
|
* 状态图表(CPU、传感器、GPU 等), SSH 终端, SFTP, Docker & 包 & 进程管理器...
|
||||||
|
* 特殊支持:生物认证、推送、桌面小部件、watchOS App、跟随系统颜色...
|
||||||
|
* 本地化 (English, 简体中文, Español, Русский язык, Português, 日本語, Deutsch, 繁體中文, Indonesian, Français, Dutch
|
||||||
BIN
fastlane/metadata/android/zh-CN/images/icon.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/4.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/5.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
fastlane/metadata/android/zh-CN/images/phoneScreenshots/6.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
1
fastlane/metadata/android/zh-CN/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
使用 Flutter 开发的服务器状态和工具箱应用
|
||||||
1
fastlane/metadata/android/zh-CN/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ServerBox
|
||||||
|
Before Width: | Height: | Size: 112 KiB |
@@ -1,28 +1,25 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- countly_flutter (24.4.0):
|
- app_links (0.0.2):
|
||||||
|
- Flutter
|
||||||
|
- camera_avfoundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_background_service_ios (0.0.3):
|
|
||||||
- Flutter
|
|
||||||
- flutter_native_splash (0.0.1):
|
- flutter_native_splash (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- icloud_storage (0.0.1):
|
- icloud_storage (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- local_auth_ios (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- permission_handler_apple (9.3.0):
|
|
||||||
- Flutter
|
|
||||||
- plain_notification_token (0.0.1):
|
- plain_notification_token (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- r_upgrade (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
@@ -34,51 +31,48 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- watch_connectivity (0.0.1):
|
- watch_connectivity (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- webview_flutter_wkwebview (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`)
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
|
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
|
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
|
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
|
||||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
|
||||||
- plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`)
|
- plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`)
|
||||||
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
|
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- 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:
|
||||||
countly_flutter:
|
app_links:
|
||||||
:path: ".symlinks/plugins/countly_flutter/ios"
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
|
camera_avfoundation:
|
||||||
|
:path: ".symlinks/plugins/camera_avfoundation/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_background_service_ios:
|
|
||||||
:path: ".symlinks/plugins/flutter_background_service_ios/ios"
|
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
icloud_storage:
|
icloud_storage:
|
||||||
:path: ".symlinks/plugins/icloud_storage/ios"
|
:path: ".symlinks/plugins/icloud_storage/ios"
|
||||||
local_auth_ios:
|
local_auth_darwin:
|
||||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
permission_handler_apple:
|
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
|
||||||
plain_notification_token:
|
plain_notification_token:
|
||||||
:path: ".symlinks/plugins/plain_notification_token/ios"
|
:path: ".symlinks/plugins/plain_notification_token/ios"
|
||||||
r_upgrade:
|
|
||||||
:path: ".symlinks/plugins/r_upgrade/ios"
|
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@@ -89,25 +83,26 @@ 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:
|
||||||
countly_flutter: 5d2febe00242796cf569662e5d47da241f31b115
|
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||||
|
camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4
|
||||||
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
|
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
|
||||||
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a
|
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a
|
||||||
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
|
||||||
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
|
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
|
||||||
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
|
|
||||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||||
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82
|
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82
|
||||||
|
webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1
|
||||||
|
|
||||||
PODFILE CHECKSUM: ec6ef69056f066e8b21a3391082f23b5ad2d37f8
|
PODFILE CHECKSUM: ec6ef69056f066e8b21a3391082f23b5ad2d37f8
|
||||||
|
|
||||||
|
|||||||
@@ -302,7 +302,6 @@
|
|||||||
E33A3E4A2A626DD0009744AB /* Embed Foundation Extensions */,
|
E33A3E4A2A626DD0009744AB /* Embed Foundation Extensions */,
|
||||||
E39515D52AB5AD64003602C1 /* Embed Watch Content */,
|
E39515D52AB5AD64003602C1 /* Embed Watch Content */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
955896919A10AA2BEC131F36 /* [CP] Copy Pods Resources */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -452,23 +451,6 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
};
|
};
|
||||||
955896919A10AA2BEC131F36 /* [CP] Copy Pods Resources */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
|
||||||
outputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -690,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 = 918;
|
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";
|
||||||
@@ -700,7 +682,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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";
|
||||||
@@ -826,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 = 918;
|
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";
|
||||||
@@ -836,7 +818,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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";
|
||||||
@@ -854,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 = 918;
|
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";
|
||||||
@@ -864,7 +846,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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";
|
||||||
@@ -885,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 = 918;
|
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;
|
||||||
@@ -898,7 +880,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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;
|
||||||
@@ -924,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 = 918;
|
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;
|
||||||
@@ -937,7 +919,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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)";
|
||||||
@@ -960,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 = 918;
|
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;
|
||||||
@@ -973,7 +955,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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)";
|
||||||
@@ -996,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 = 918;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1008,7 +990,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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;
|
||||||
@@ -1037,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 = 918;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1049,7 +1031,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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;
|
||||||
@@ -1075,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 = 918;
|
CURRENT_PROJECT_VERSION = 1104;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1087,7 +1069,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.918;
|
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;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import UIKit
|
|||||||
import WidgetKit
|
import WidgetKit
|
||||||
import Flutter
|
import Flutter
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -1 +1,37 @@
|
|||||||
{"images":[{"scale":"3x","idiom":"universal","filename":"AppIcon-29.0x29.0@3x.png","size":"29x29","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-29.0x29.0@2x.png","size":"29x29","platform":"ios"},{"scale":"3x","idiom":"universal","filename":"AppIcon-64.0x64.0@3x.png","size":"64x64","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-20.0x20.0@2x.png","size":"20x20","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-60.0x60.0@2x.png","size":"60x60","platform":"ios"},{"scale":"3x","idiom":"universal","filename":"AppIcon-40.0x40.0@3x.png","size":"40x40","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-76.0x76.0@2x.png","size":"76x76","platform":"ios"},{"scale":"3x","idiom":"universal","filename":"AppIcon-38.0x38.0@3x.png","size":"38x38","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-68.0x68.0@2x.png","size":"68x68","platform":"ios"},{"scale":"1x","idiom":"universal","filename":"AppIcon-1024.0x1024.0@1x.png","size":"1024x1024","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-64.0x64.0@2x.png","size":"64x64","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-40.0x40.0@2x.png","size":"40x40","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-83.5x83.5@2x.png","size":"83.5x83.5","platform":"ios"},{"scale":"3x","idiom":"universal","filename":"AppIcon-20.0x20.0@3x.png","size":"20x20","platform":"ios"},{"scale":"2x","idiom":"universal","filename":"AppIcon-38.0x38.0@2x.png","size":"38x38","platform":"ios"},{"scale":"3x","idiom":"universal","filename":"AppIcon-60.0x60.0@3x.png","size":"60x60","platform":"ios"}],"info":{"version":1,"author":"appicon"}}
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Icon-1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "icon-1024 1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "tinted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024 1.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
@@ -1,76 +1,81 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<string>zh</string>
|
<string>zh</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>ServerBox</string>
|
<string>ServerBox</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false />
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_dartobservatory._tcp</string>
|
<string>_dartobservatory._tcp</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<string>Required for auth</string>
|
<array>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<string>ConfigurationIntent</string>
|
||||||
<string>ServerBox needs to access your local network to discover and connect to your server.</string>
|
</array>
|
||||||
<key>NSUserActivityTypes</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<array>
|
<true />
|
||||||
<string>ConfigurationIntent</string>
|
<key>UIBackgroundModes</key>
|
||||||
</array>
|
<array>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<string>fetch</string>
|
||||||
<true/>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<array>
|
<string>LaunchScreen</string>
|
||||||
<string>fetch</string>
|
<key>UIMainStoryboardFile</key>
|
||||||
</array>
|
<string>Main</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<string>LaunchScreen</string>
|
<false />
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<string>Main</string>
|
<array>
|
||||||
<key>UIStatusBarHidden</key>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<false/>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<array>
|
</array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<array>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<array>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
</array>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<false />
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<string>Access your local network to discover and connect to your server.</string>
|
||||||
<false/>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
</dict>
|
<string>Required for auth</string>
|
||||||
</plist>
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Scan QR codes and etc.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Get QR code and etc.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -64,9 +64,14 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>_dartobservatory._tcp</string>
|
<string>_dartobservatory._tcp</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<string>ServerBox needs to access your local network to discover and connect to your server.</string>
|
<string>Access your local network to discover and connect to your server.</string>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Required for auth</string>
|
<string>Required for auth</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Scan QR codes and etc.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Get QR code and etc.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -28,13 +28,13 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false />
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false/>
|
<false />
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
@@ -59,8 +59,13 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false />
|
||||||
|
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Required for auth</string>
|
<string>Required for auth</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Scan QR codes and etc.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Get QR code and etc.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
99
lib/app.dart
@@ -1,12 +1,16 @@
|
|||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:fl_lib/l10n/gen/lib_l10n.dart';
|
import 'package:fl_lib/l10n/gen_l10n/lib_l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:toolbox/data/res/build_data.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/data/res/rebuild.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/data/res/rebuild.dart';
|
||||||
import 'package:toolbox/view/page/home/home.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
import 'package:server_box/view/page/home/home.dart';
|
||||||
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
|
|
||||||
|
part 'intro.dart';
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
@@ -15,11 +19,25 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_setup(context);
|
_setup(context);
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: RebuildNodes.app,
|
listenable: RNodes.app,
|
||||||
builder: (_, __) {
|
builder: (context, _) {
|
||||||
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
if (!Stores.setting.useSystemPrimaryColor.fetch()) {
|
||||||
UIs.colorSeed = Color(Stores.setting.primaryColor.fetch());
|
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
||||||
return _buildApp(context);
|
UIs.colorSeed = colorSeed;
|
||||||
|
// Past code uses [UIs.primaryColor] as the primary color
|
||||||
|
UIs.primaryColor = colorSeed;
|
||||||
|
return _buildApp(
|
||||||
|
context,
|
||||||
|
light: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorSchemeSeed: UIs.colorSeed,
|
||||||
|
),
|
||||||
|
dark: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
colorSchemeSeed: UIs.colorSeed,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: (light, dark) {
|
builder: (light, dark) {
|
||||||
@@ -32,10 +50,10 @@ class MyApp extends StatelessWidget {
|
|||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
colorScheme: dark,
|
colorScheme: dark,
|
||||||
);
|
);
|
||||||
if (context.isDark && light != null) {
|
if (context.isDark && dark != null) {
|
||||||
UIs.primaryColor = light.primary;
|
|
||||||
} else if (!context.isDark && dark != null) {
|
|
||||||
UIs.primaryColor = dark.primary;
|
UIs.primaryColor = dark.primary;
|
||||||
|
} else if (!context.isDark && light != null) {
|
||||||
|
UIs.primaryColor = light.primary;
|
||||||
}
|
}
|
||||||
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
||||||
},
|
},
|
||||||
@@ -44,7 +62,8 @@ class MyApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildApp(BuildContext ctx, {ThemeData? light, ThemeData? dark}) {
|
Widget _buildApp(BuildContext ctx,
|
||||||
|
{required ThemeData light, required ThemeData dark}) {
|
||||||
final tMode = Stores.setting.themeMode.fetch();
|
final tMode = Stores.setting.themeMode.fetch();
|
||||||
// Issue #57
|
// Issue #57
|
||||||
final themeMode = switch (tMode) {
|
final themeMode = switch (tMode) {
|
||||||
@@ -54,52 +73,40 @@ class MyApp extends StatelessWidget {
|
|||||||
};
|
};
|
||||||
final locale = Stores.setting.locale.fetch().toLocale;
|
final locale = Stores.setting.locale.fetch().toLocale;
|
||||||
|
|
||||||
light ??= ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
colorSchemeSeed: UIs.colorSeed,
|
|
||||||
);
|
|
||||||
dark ??= ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
colorSchemeSeed: UIs.colorSeed,
|
|
||||||
);
|
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
key: ValueKey(locale),
|
||||||
locale: locale,
|
locale: locale,
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
LibLocalizations.delegate,
|
LibLocalizations.delegate,
|
||||||
...AppLocalizations.localizationsDelegates,
|
...AppLocalizations.localizationsDelegates,
|
||||||
],
|
],
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
localeListResolutionCallback: LocaleUtil.resolve,
|
||||||
|
navigatorObservers: [AppRouteObserver.instance],
|
||||||
title: BuildData.name,
|
title: BuildData.name,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
theme: light,
|
theme: light.fixWindowsFont,
|
||||||
darkTheme: tMode < 3 ? dark : _getAmoledTheme(dark),
|
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
|
||||||
home: _buildAppContent(ctx),
|
home: VirtualWindowFrame(
|
||||||
);
|
child: Builder(
|
||||||
}
|
builder: (context) {
|
||||||
|
context.setLibL10n();
|
||||||
|
final appL10n = AppLocalizations.of(context);
|
||||||
|
if (appL10n != null) l10n = appL10n;
|
||||||
|
|
||||||
Widget _buildAppContent(BuildContext ctx) {
|
final intros = _IntroPage.builders;
|
||||||
//if (Pros.app.isWearOS) return const WearHome();
|
if (intros.isNotEmpty) {
|
||||||
return const HomePage();
|
return _IntroPage(intros);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const HomePage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setup(BuildContext context) async {
|
void _setup(BuildContext context) async {
|
||||||
SystemUIs.setTransparentNavigationBar(context);
|
SystemUIs.setTransparentNavigationBar(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData _getAmoledTheme(ThemeData darkTheme) => darkTheme.copyWith(
|
|
||||||
scaffoldBackgroundColor: Colors.black,
|
|
||||||
dialogBackgroundColor: Colors.black,
|
|
||||||
drawerTheme: const DrawerThemeData(backgroundColor: Colors.black),
|
|
||||||
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
|
|
||||||
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
|
|
||||||
bottomSheetTheme:
|
|
||||||
const BottomSheetThemeData(backgroundColor: Colors.black),
|
|
||||||
listTileTheme: const ListTileThemeData(tileColor: Colors.transparent),
|
|
||||||
cardTheme: const CardTheme(color: Colors.black12),
|
|
||||||
navigationBarTheme:
|
|
||||||
const NavigationBarThemeData(backgroundColor: Colors.black),
|
|
||||||
popupMenuTheme: const PopupMenuThemeData(color: Colors.black),
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:toolbox/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
abstract final class BgRunMC {
|
abstract final class BgRunMC {
|
||||||
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain');
|
static const _channel = MethodChannel('${Miscs.pkgName}/app_retain');
|
||||||
@@ -11,4 +11,8 @@ abstract final class BgRunMC {
|
|||||||
static void startService() {
|
static void startService() {
|
||||||
_channel.invokeMethod('startService');
|
_channel.invokeMethod('startService');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void stopService() {
|
||||||
|
_channel.invokeMethod('stopService');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:toolbox/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
abstract final class HomeWidgetMC {
|
abstract final class HomeWidgetMC {
|
||||||
static const _channel = MethodChannel('${Miscs.pkgName}/home_widget');
|
static const _channel = MethodChannel('${Miscs.pkgName}/home_widget');
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import 'package:toolbox/data/res/build_data.dart';
|
|
||||||
|
|
||||||
extension BuildDataX on BuildData {
|
|
||||||
static const versionStr = 'v1.0.${BuildData.build}';
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
|
import 'package:server_box/view/widget/unix_perm.dart';
|
||||||
|
|
||||||
extension SftpFileX on SftpFileMode {
|
extension SftpFileX on SftpFileMode {
|
||||||
String get str {
|
String get str {
|
||||||
@@ -8,6 +9,26 @@ extension SftpFileX on SftpFileMode {
|
|||||||
|
|
||||||
return '$user$group$other';
|
return '$user$group$other';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnixPerm toUnixPerm() {
|
||||||
|
return UnixPerm(
|
||||||
|
user: RWX(
|
||||||
|
r: userRead,
|
||||||
|
w: userWrite,
|
||||||
|
x: userExecute,
|
||||||
|
),
|
||||||
|
group: RWX(
|
||||||
|
r: groupRead,
|
||||||
|
w: groupWrite,
|
||||||
|
x: groupExecute,
|
||||||
|
),
|
||||||
|
other: RWX(
|
||||||
|
r: otherRead,
|
||||||
|
w: otherWrite,
|
||||||
|
x: otherExecute,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getRoleMode(bool r, bool w, bool x) {
|
String _getRoleMode(bool r, bool w, bool x) {
|
||||||
|
|||||||
@@ -5,72 +5,74 @@ import 'package:dartssh2/dartssh2.dart';
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import '../../data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
typedef _OnStdout = void Function(String data, SSHSession session);
|
typedef OnStdout = void Function(String data, SSHSession session);
|
||||||
typedef _OnStdin = void Function(SSHSession session);
|
typedef OnStdin = void Function(SSHSession session);
|
||||||
|
|
||||||
typedef PwdRequestFunc = Future<String?> Function(String? user);
|
typedef PwdRequestFunc = Future<String?> Function(String? user);
|
||||||
|
|
||||||
extension SSHClientX on SSHClient {
|
extension SSHClientX on SSHClient {
|
||||||
Future<SSHSession> exec(
|
Future<(SSHSession, String)> exec(
|
||||||
String cmd, {
|
OnStdin onStdin, {
|
||||||
_OnStdout? onStderr,
|
String? entry,
|
||||||
_OnStdout? onStdout,
|
SSHPtyConfig? pty,
|
||||||
_OnStdin? stdin,
|
OnStdout? onStdout,
|
||||||
bool redirectToBash = false, // not working yet. do not use
|
OnStdout? onStderr,
|
||||||
|
bool stdout = true,
|
||||||
|
bool stderr = true,
|
||||||
|
Map<String, String>? env,
|
||||||
}) async {
|
}) async {
|
||||||
final session = await execute(redirectToBash ? "head -1 | bash" : cmd);
|
final session = await execute(
|
||||||
|
entry ?? 'cat | sh',
|
||||||
if (redirectToBash) {
|
pty: pty,
|
||||||
session.stdin.add("$cmd\n".uint8List);
|
environment: env,
|
||||||
}
|
);
|
||||||
|
|
||||||
|
final result = BytesBuilder(copy: false);
|
||||||
final stdoutDone = Completer<void>();
|
final stdoutDone = Completer<void>();
|
||||||
final stderrDone = Completer<void>();
|
final stderrDone = Completer<void>();
|
||||||
|
|
||||||
if (onStdout != null) {
|
session.stdout.listen(
|
||||||
session.stdout.listen(
|
(e) {
|
||||||
(e) => onStdout(e.string, session),
|
onStdout?.call(e.string, session);
|
||||||
onDone: stdoutDone.complete,
|
if (stdout) result.add(e);
|
||||||
);
|
},
|
||||||
} else {
|
onDone: stdoutDone.complete,
|
||||||
stdoutDone.complete();
|
onError: stderrDone.completeError,
|
||||||
}
|
);
|
||||||
|
|
||||||
if (onStderr != null) {
|
session.stderr.listen(
|
||||||
session.stderr.listen(
|
(e) {
|
||||||
(e) => onStderr(e.string, session),
|
onStderr?.call(e.string, session);
|
||||||
onDone: stderrDone.complete,
|
if (stderr) result.add(e);
|
||||||
);
|
},
|
||||||
} else {
|
onDone: stderrDone.complete,
|
||||||
stderrDone.complete();
|
onError: stderrDone.completeError,
|
||||||
}
|
);
|
||||||
|
|
||||||
if (stdin != null) {
|
onStdin(session);
|
||||||
stdin(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
await stdoutDone.future;
|
await stdoutDone.future;
|
||||||
await stderrDone.future;
|
await stderrDone.future;
|
||||||
|
|
||||||
session.close();
|
return (session, result.takeBytes().string);
|
||||||
return session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> execWithPwd(
|
Future<int?> execWithPwd(
|
||||||
String cmd, {
|
String script, {
|
||||||
|
String? entry,
|
||||||
BuildContext? context,
|
BuildContext? context,
|
||||||
_OnStdout? onStdout,
|
OnStdout? onStdout,
|
||||||
_OnStdout? onStderr,
|
OnStdout? onStderr,
|
||||||
_OnStdin? stdin,
|
|
||||||
bool redirectToBash = false, // not working yet. do not use
|
|
||||||
required String id,
|
required String id,
|
||||||
}) async {
|
}) async {
|
||||||
var isRequestingPwd = false;
|
var isRequestingPwd = false;
|
||||||
final session = await exec(
|
final (session, _) = await exec(
|
||||||
cmd,
|
(sess) {
|
||||||
redirectToBash: redirectToBash,
|
sess.stdin.add('$script\n'.uint8List);
|
||||||
|
sess.stdin.close();
|
||||||
|
},
|
||||||
onStderr: (data, session) async {
|
onStderr: (data, session) async {
|
||||||
onStderr?.call(data, session);
|
onStderr?.call(data, session);
|
||||||
if (isRequestingPwd) return;
|
if (isRequestingPwd) return;
|
||||||
@@ -79,58 +81,42 @@ extension SSHClientX on SSHClient {
|
|||||||
isRequestingPwd = true;
|
isRequestingPwd = true;
|
||||||
final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1);
|
final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1);
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
final pwd = await context.showPwdDialog(title: user, id: id);
|
final pwd = context.mounted
|
||||||
|
? await context.showPwdDialog(title: user, id: id)
|
||||||
|
: null;
|
||||||
if (pwd == null || pwd.isEmpty) {
|
if (pwd == null || pwd.isEmpty) {
|
||||||
session.kill(SSHSignal.TERM);
|
session.stdin.close();
|
||||||
} else {
|
} else {
|
||||||
session.stdin.add('$pwd\n'.uint8List);
|
session.stdin.add('$pwd\n'.uint8List);
|
||||||
}
|
}
|
||||||
isRequestingPwd = false;
|
isRequestingPwd = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStdout: (data, sink) async {
|
onStdout: onStdout,
|
||||||
onStdout?.call(data, sink);
|
entry: entry,
|
||||||
},
|
|
||||||
stdin: stdin,
|
|
||||||
);
|
);
|
||||||
return session.exitCode;
|
return session.exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> runForOutput(
|
Future<String> execForOutput(
|
||||||
String command, {
|
String script, {
|
||||||
bool runInPty = false,
|
SSHPtyConfig? pty,
|
||||||
bool stdout = true,
|
bool stdout = true,
|
||||||
bool stderr = true,
|
bool stderr = true,
|
||||||
Map<String, String>? environment,
|
String? entry,
|
||||||
Future<void> Function(SSHSession)? action,
|
Map<String, String>? env,
|
||||||
}) async {
|
}) async {
|
||||||
final session = await execute(
|
final ret = await exec(
|
||||||
command,
|
(session) {
|
||||||
pty: runInPty ? const SSHPtyConfig() : null,
|
session.stdin.add('$script\n'.uint8List);
|
||||||
environment: environment,
|
session.stdin.close();
|
||||||
|
},
|
||||||
|
pty: pty,
|
||||||
|
env: env,
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
entry: entry,
|
||||||
);
|
);
|
||||||
|
return ret.$2;
|
||||||
final result = BytesBuilder(copy: false);
|
|
||||||
final stdoutDone = Completer<void>();
|
|
||||||
final stderrDone = Completer<void>();
|
|
||||||
|
|
||||||
session.stdout.listen(
|
|
||||||
stdout ? result.add : (_) {},
|
|
||||||
onDone: stdoutDone.complete,
|
|
||||||
onError: stderrDone.completeError,
|
|
||||||
);
|
|
||||||
|
|
||||||
session.stderr.listen(
|
|
||||||
stderr ? result.add : (_) {},
|
|
||||||
onDone: stderrDone.complete,
|
|
||||||
onError: stderrDone.completeError,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (action != null) await action(session);
|
|
||||||
|
|
||||||
await stdoutDone.future;
|
|
||||||
await stderrDone.future;
|
|
||||||
|
|
||||||
return result.takeBytes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,29 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/res/build_data.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:server_box/view/page/container.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/view/page/home/home.dart';
|
||||||
import 'package:toolbox/view/page/backup.dart';
|
import 'package:server_box/view/page/iperf.dart';
|
||||||
import 'package:toolbox/view/page/container.dart';
|
import 'package:server_box/view/page/ping.dart';
|
||||||
import 'package:toolbox/view/page/home/home.dart';
|
import 'package:server_box/view/page/private_key/edit.dart';
|
||||||
import 'package:toolbox/view/page/iperf.dart';
|
import 'package:server_box/view/page/pve.dart';
|
||||||
import 'package:toolbox/view/page/ping.dart';
|
import 'package:server_box/view/page/server/detail/view.dart';
|
||||||
import 'package:toolbox/view/page/private_key/edit.dart';
|
import 'package:server_box/view/page/setting/platform/android.dart';
|
||||||
import 'package:toolbox/view/page/private_key/list.dart';
|
import 'package:server_box/view/page/setting/platform/ios.dart';
|
||||||
import 'package:toolbox/view/page/pve.dart';
|
import 'package:server_box/view/page/setting/seq/srv_func_seq.dart';
|
||||||
import 'package:toolbox/view/page/server/detail/view.dart';
|
import 'package:server_box/view/page/snippet/result.dart';
|
||||||
import 'package:toolbox/view/page/setting/platform/android.dart';
|
import 'package:server_box/view/page/ssh/page.dart';
|
||||||
import 'package:toolbox/view/page/setting/platform/ios.dart';
|
import 'package:server_box/view/page/setting/seq/virt_key.dart';
|
||||||
import 'package:toolbox/view/page/setting/seq/srv_func_seq.dart';
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
import 'package:toolbox/view/page/snippet/result.dart';
|
import 'package:server_box/view/page/process.dart';
|
||||||
import 'package:toolbox/view/page/ssh/page.dart';
|
import 'package:server_box/view/page/server/tab.dart';
|
||||||
import 'package:toolbox/view/page/setting/seq/virt_key.dart';
|
import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart';
|
||||||
import 'package:toolbox/view/page/storage/local.dart';
|
import 'package:server_box/view/page/setting/seq/srv_seq.dart';
|
||||||
|
import 'package:server_box/view/page/snippet/edit.dart';
|
||||||
import '../data/model/server/snippet.dart';
|
import 'package:server_box/view/page/storage/sftp.dart';
|
||||||
import '../view/page/editor.dart';
|
import 'package:server_box/view/page/storage/sftp_mission.dart';
|
||||||
import '../view/page/process.dart';
|
|
||||||
import '../view/page/server/edit.dart';
|
|
||||||
import '../view/page/server/tab.dart';
|
|
||||||
import '../view/page/setting/entry.dart';
|
|
||||||
import '../view/page/setting/seq/srv_detail_seq.dart';
|
|
||||||
import '../view/page/setting/seq/srv_seq.dart';
|
|
||||||
import '../view/page/snippet/edit.dart';
|
|
||||||
import '../view/page/snippet/list.dart';
|
|
||||||
import '../view/page/storage/sftp.dart';
|
|
||||||
import '../view/page/storage/sftp_mission.dart';
|
|
||||||
|
|
||||||
class AppRoutes {
|
class AppRoutes {
|
||||||
final Widget page;
|
final Widget page;
|
||||||
@@ -61,7 +50,7 @@ class AppRoutes {
|
|||||||
return Future.value(null);
|
return Future.value(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes serverDetail({Key? key, required ServerPrivateInfo spi}) {
|
static AppRoutes serverDetail({Key? key, required Spi spi}) {
|
||||||
return AppRoutes(ServerDetailPage(key: key, spi: spi), 'server_detail');
|
return AppRoutes(ServerDetailPage(key: key, spi: spi), 'server_detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,13 +58,6 @@ class AppRoutes {
|
|||||||
return AppRoutes(ServerPage(key: key), 'server_tab');
|
return AppRoutes(ServerPage(key: key), 'server_tab');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes serverEdit({Key? key, ServerPrivateInfo? spi}) {
|
|
||||||
return AppRoutes(
|
|
||||||
ServerEditPage(spi: spi),
|
|
||||||
'server_${spi == null ? 'add' : 'edit'}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes keyEdit({Key? key, PrivateKeyInfo? pki}) {
|
static AppRoutes keyEdit({Key? key, PrivateKeyInfo? pki}) {
|
||||||
return AppRoutes(
|
return AppRoutes(
|
||||||
PrivateKeyEditPage(pki: pki),
|
PrivateKeyEditPage(pki: pki),
|
||||||
@@ -83,10 +65,6 @@ class AppRoutes {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes keyList({Key? key}) {
|
|
||||||
return AppRoutes(PrivateKeysListPage(key: key), 'key_detail');
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes snippetEdit({Key? key, Snippet? snippet}) {
|
static AppRoutes snippetEdit({Key? key, Snippet? snippet}) {
|
||||||
return AppRoutes(
|
return AppRoutes(
|
||||||
SnippetEditPage(snippet: snippet),
|
SnippetEditPage(snippet: snippet),
|
||||||
@@ -94,20 +72,18 @@ class AppRoutes {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes snippetList({Key? key}) {
|
|
||||||
return AppRoutes(SnippetListPage(key: key), 'snippet_detail');
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes ssh({
|
static AppRoutes ssh({
|
||||||
Key? key,
|
Key? key,
|
||||||
required ServerPrivateInfo spi,
|
required Spi spi,
|
||||||
String? initCmd,
|
String? initCmd,
|
||||||
|
Snippet? initSnippet,
|
||||||
}) {
|
}) {
|
||||||
return AppRoutes(
|
return AppRoutes(
|
||||||
SSHPage(
|
SSHPage(
|
||||||
key: key,
|
key: key,
|
||||||
spi: spi,
|
spi: spi,
|
||||||
initCmd: initCmd,
|
initCmd: initCmd,
|
||||||
|
initSnippet: initSnippet,
|
||||||
),
|
),
|
||||||
'ssh_term',
|
'ssh_term',
|
||||||
);
|
);
|
||||||
@@ -117,26 +93,12 @@ class AppRoutes {
|
|||||||
return AppRoutes(SSHVirtKeySettingPage(key: key), 'ssh_virt_key_setting');
|
return AppRoutes(SSHVirtKeySettingPage(key: key), 'ssh_virt_key_setting');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes localStorage(
|
|
||||||
{Key? key, bool isPickFile = false, String? initDir}) {
|
|
||||||
return AppRoutes(
|
|
||||||
LocalStoragePage(
|
|
||||||
key: key,
|
|
||||||
isPickFile: isPickFile,
|
|
||||||
initDir: initDir,
|
|
||||||
),
|
|
||||||
'local_storage');
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes sftpMission({Key? key}) {
|
static AppRoutes sftpMission({Key? key}) {
|
||||||
return AppRoutes(SftpMissionPage(key: key), 'sftp_mission');
|
return AppRoutes(SftpMissionPage(key: key), 'sftp_mission');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes sftp(
|
static AppRoutes sftp(
|
||||||
{Key? key,
|
{Key? key, required Spi spi, String? initPath, bool isSelect = false}) {
|
||||||
required ServerPrivateInfo spi,
|
|
||||||
String? initPath,
|
|
||||||
bool isSelect = false}) {
|
|
||||||
return AppRoutes(
|
return AppRoutes(
|
||||||
SftpPage(
|
SftpPage(
|
||||||
key: key,
|
key: key,
|
||||||
@@ -147,48 +109,10 @@ class AppRoutes {
|
|||||||
'sftp');
|
'sftp');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes backup({Key? key}) {
|
static AppRoutes docker({Key? key, required Spi spi}) {
|
||||||
return AppRoutes(BackupPage(key: key), 'backup');
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes debug({Key? key}) {
|
|
||||||
return AppRoutes(
|
|
||||||
DebugPage(
|
|
||||||
key: key,
|
|
||||||
args: DebugPageArgs(
|
|
||||||
notifier: Pros.debug.widgets,
|
|
||||||
onClear: Pros.debug.clear,
|
|
||||||
title: 'Logs(${BuildData.build})',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'debug',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes docker({Key? key, required ServerPrivateInfo spi}) {
|
|
||||||
return AppRoutes(ContainerPage(key: key, spi: spi), 'docker');
|
return AppRoutes(ContainerPage(key: key, spi: spi), 'docker');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - Pop true if the text is changed & [path] is not null
|
|
||||||
/// - Pop text if [path] is null
|
|
||||||
static AppRoutes editor({
|
|
||||||
Key? key,
|
|
||||||
String? path,
|
|
||||||
String? text,
|
|
||||||
String? langCode,
|
|
||||||
String? title,
|
|
||||||
}) {
|
|
||||||
return AppRoutes(
|
|
||||||
EditorPage(
|
|
||||||
key: key,
|
|
||||||
path: path,
|
|
||||||
text: text,
|
|
||||||
langCode: langCode,
|
|
||||||
title: title,
|
|
||||||
),
|
|
||||||
'editor');
|
|
||||||
}
|
|
||||||
|
|
||||||
// static AppRoutes fullscreen({Key? key}) {
|
// static AppRoutes fullscreen({Key? key}) {
|
||||||
// return AppRoutes(FullScreenPage(key: key), 'fullscreen');
|
// return AppRoutes(FullScreenPage(key: key), 'fullscreen');
|
||||||
// }
|
// }
|
||||||
@@ -201,14 +125,10 @@ class AppRoutes {
|
|||||||
return AppRoutes(PingPage(key: key), 'ping');
|
return AppRoutes(PingPage(key: key), 'ping');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes process({Key? key, required ServerPrivateInfo spi}) {
|
static AppRoutes process({Key? key, required Spi spi}) {
|
||||||
return AppRoutes(ProcessPage(key: key, spi: spi), 'process');
|
return AppRoutes(ProcessPage(key: key, spi: spi), 'process');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes settings({Key? key}) {
|
|
||||||
return AppRoutes(SettingPage(key: key), 'setting');
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppRoutes serverOrder({Key? key}) {
|
static AppRoutes serverOrder({Key? key}) {
|
||||||
return AppRoutes(ServerOrderPage(key: key), 'server_order');
|
return AppRoutes(ServerOrderPage(key: key), 'server_order');
|
||||||
}
|
}
|
||||||
@@ -235,7 +155,7 @@ class AppRoutes {
|
|||||||
'snippet_result');
|
'snippet_result');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes iperf({Key? key, required ServerPrivateInfo spi}) {
|
static AppRoutes iperf({Key? key, required Spi spi}) {
|
||||||
return AppRoutes(IPerfPage(key: key, spi: spi), 'iperf');
|
return AppRoutes(IPerfPage(key: key, spi: spi), 'iperf');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +163,7 @@ class AppRoutes {
|
|||||||
return AppRoutes(ServerFuncBtnsOrderPage(key: key), 'server_func_btns_seq');
|
return AppRoutes(ServerFuncBtnsOrderPage(key: key), 'server_func_btns_seq');
|
||||||
}
|
}
|
||||||
|
|
||||||
static AppRoutes pve({Key? key, required ServerPrivateInfo spi}) {
|
static AppRoutes pve({Key? key, required Spi spi}) {
|
||||||
return AppRoutes(PvePage(key: key, spi: spi), 'pve');
|
return AppRoutes(PvePage(key: key, spi: spi), 'pve');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
lib/core/sync.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:server_box/data/model/app/backup.dart';
|
||||||
|
import 'package:server_box/data/store/no_backup.dart';
|
||||||
|
|
||||||
|
const bakSync = BakSyncer._();
|
||||||
|
|
||||||
|
final class BakSyncer extends SyncIface<Backup> {
|
||||||
|
const BakSyncer._() : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> saveToFile() => Backup.backup();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Backup> fromFile(String path) async {
|
||||||
|
final content = await File(path).readAsString();
|
||||||
|
return Backup.fromJsonString(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<RemoteStorage?> get remoteStorage async {
|
||||||
|
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;
|
||||||
|
|
||||||
|
final webdavEnabled = settings.webdavSync.fetch();
|
||||||
|
if (webdavEnabled) return webdav;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,9 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:plain_notification_token/plain_notification_token.dart';
|
import 'package:plain_notification_token/plain_notification_token.dart';
|
||||||
|
|
||||||
Future<String?> getToken() async {
|
Future<String?> getToken() async {
|
||||||
if (isIOS) {
|
if (!isIOS) return null;
|
||||||
final plainNotificationToken = PlainNotificationToken();
|
final instance = ApnsToken()..requestPermission();
|
||||||
plainNotificationToken.requestPermission();
|
// Wait until Permission dialog closed
|
||||||
|
await instance.onIosSettingsRegistered.first;
|
||||||
// If you want to wait until Permission dialog close,
|
return await instance.getToken();
|
||||||
// you need wait changing setting registered.
|
|
||||||
await plainNotificationToken.onIosSettingsRegistered.first;
|
|
||||||
return await plainNotificationToken.getToken();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:toolbox/data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '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.
|
||||||
///
|
///
|
||||||
@@ -42,7 +43,7 @@ String getPrivateKey(String id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<SSHClient> genClient(
|
Future<SSHClient> genClient(
|
||||||
ServerPrivateInfo spi, {
|
Spi spi, {
|
||||||
void Function(GenSSHClientStatus)? onStatus,
|
void Function(GenSSHClientStatus)? onStatus,
|
||||||
|
|
||||||
/// Only pass this param if using multi-threading and key login
|
/// Only pass this param if using multi-threading and key login
|
||||||
@@ -52,16 +53,18 @@ Future<SSHClient> genClient(
|
|||||||
String? jumpPrivateKey,
|
String? jumpPrivateKey,
|
||||||
Duration timeout = const Duration(seconds: 5),
|
Duration timeout = const Duration(seconds: 5),
|
||||||
|
|
||||||
/// [ServerPrivateInfo] of the jump server
|
/// [Spi] of the jump server
|
||||||
///
|
///
|
||||||
/// Must pass this param if using multi-threading and key login
|
/// Must pass this param if using multi-threading and key login
|
||||||
ServerPrivateInfo? jumpSpi,
|
Spi? jumpSpi,
|
||||||
|
|
||||||
/// Handle keyboard-interactive authentication
|
/// Handle keyboard-interactive authentication
|
||||||
FutureOr<List<String>?> Function(SSHUserInfoRequest)? onKeyboardInteractive,
|
FutureOr<List<String>?> Function(SSHUserInfoRequest)? onKeyboardInteractive,
|
||||||
}) async {
|
}) async {
|
||||||
onStatus?.call(GenSSHClientStatus.socket);
|
onStatus?.call(GenSSHClientStatus.socket);
|
||||||
|
|
||||||
|
String? alterUser;
|
||||||
|
|
||||||
final socket = await () async {
|
final socket = await () async {
|
||||||
// Proxy
|
// Proxy
|
||||||
final jumpSpi_ = () {
|
final jumpSpi_ = () {
|
||||||
@@ -91,15 +94,18 @@ Future<SSHClient> genClient(
|
|||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Loggers.app.warning('genClient', e);
|
||||||
if (spi.alterUrl == null) rethrow;
|
if (spi.alterUrl == null) rethrow;
|
||||||
try {
|
try {
|
||||||
final ipPort = spi.fromStringUrl();
|
final res = spi.fromStringUrl();
|
||||||
|
alterUser = res.$2;
|
||||||
return await SSHSocket.connect(
|
return await SSHSocket.connect(
|
||||||
ipPort.ip,
|
res.$1,
|
||||||
ipPort.port,
|
res.$3,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Loggers.app.warning('genClient alterUrl', e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +116,7 @@ Future<SSHClient> genClient(
|
|||||||
onStatus?.call(GenSSHClientStatus.pwd);
|
onStatus?.call(GenSSHClientStatus.pwd);
|
||||||
return SSHClient(
|
return SSHClient(
|
||||||
socket,
|
socket,
|
||||||
username: spi.user,
|
username: alterUser ?? spi.user,
|
||||||
onPasswordRequest: () => spi.pwd,
|
onPasswordRequest: () => spi.pwd,
|
||||||
onUserInfoRequest: onKeyboardInteractive,
|
onUserInfoRequest: onKeyboardInteractive,
|
||||||
// printDebug: debugPrint,
|
// printDebug: debugPrint,
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:server_box/data/provider/app.dart';
|
||||||
|
|
||||||
abstract final class KeybordInteractive {
|
abstract final class KeybordInteractive {
|
||||||
static FutureOr<List<String>?> defaultHandle(
|
static FutureOr<List<String>?> defaultHandle(
|
||||||
ServerPrivateInfo spi, {
|
Spi spi, {
|
||||||
BuildContext? ctx,
|
BuildContext? ctx,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final res = await (ctx ?? Pros.app.ctx)?.showPwdDialog(
|
final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog(
|
||||||
title: '2FA ${l10n.pwd}',
|
title: l10n.pwd,
|
||||||
id: spi.id,
|
id: spi.id,
|
||||||
label: spi.id,
|
label: spi.id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:computer/computer.dart';
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
|
||||||
import 'package:icloud_storage/icloud_storage.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:toolbox/data/model/app/backup.dart';
|
|
||||||
import 'package:toolbox/data/model/app/sync.dart';
|
|
||||||
|
|
||||||
import '../../../data/model/app/error.dart';
|
|
||||||
|
|
||||||
abstract final class ICloud {
|
|
||||||
static const _containerId = 'iCloud.tech.lolli.serverbox';
|
|
||||||
|
|
||||||
static final _logger = Logger('iCloud');
|
|
||||||
|
|
||||||
/// Upload file to iCloud
|
|
||||||
///
|
|
||||||
/// - [relativePath] is the path relative to [Paths.doc],
|
|
||||||
/// must not starts with `/`
|
|
||||||
/// - [localPath] has higher priority than [relativePath], but only apply
|
|
||||||
/// to the local path instead of iCloud path
|
|
||||||
///
|
|
||||||
/// Return [null] if upload success, [ICloudErr] otherwise
|
|
||||||
static Future<ICloudErr?> upload({
|
|
||||||
required String relativePath,
|
|
||||||
String? localPath,
|
|
||||||
}) async {
|
|
||||||
final completer = Completer<ICloudErr?>();
|
|
||||||
try {
|
|
||||||
await ICloudStorage.upload(
|
|
||||||
containerId: _containerId,
|
|
||||||
filePath: localPath ?? '${Paths.doc}/$relativePath',
|
|
||||||
destinationRelativePath: relativePath,
|
|
||||||
onProgress: (stream) {
|
|
||||||
stream.listen(
|
|
||||||
null,
|
|
||||||
onDone: () => completer.complete(null),
|
|
||||||
onError: (e) => completer.complete(
|
|
||||||
ICloudErr(type: ICloudErrType.generic, message: '$e'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Upload $relativePath failed', e, s);
|
|
||||||
completer.complete(ICloudErr(type: ICloudErrType.generic, message: '$e'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<ICloudFile>> getAll() async {
|
|
||||||
return await ICloudStorage.gather(
|
|
||||||
containerId: _containerId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> delete(String relativePath) async {
|
|
||||||
try {
|
|
||||||
await ICloudStorage.delete(
|
|
||||||
containerId: _containerId,
|
|
||||||
relativePath: relativePath,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Delete $relativePath failed', e, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Download file from iCloud
|
|
||||||
///
|
|
||||||
/// - [relativePath] is the path relative to [Paths.doc],
|
|
||||||
/// must not starts with `/`
|
|
||||||
/// - [localPath] has higher priority than [relativePath], but only apply
|
|
||||||
/// to the local path instead of iCloud path
|
|
||||||
///
|
|
||||||
/// Return `null` if upload success, [ICloudErr] otherwise
|
|
||||||
static Future<ICloudErr?> download({
|
|
||||||
required String relativePath,
|
|
||||||
String? localPath,
|
|
||||||
}) async {
|
|
||||||
final completer = Completer<ICloudErr?>();
|
|
||||||
try {
|
|
||||||
await ICloudStorage.download(
|
|
||||||
containerId: _containerId,
|
|
||||||
relativePath: relativePath,
|
|
||||||
destinationFilePath: localPath ?? '${Paths.doc}/$relativePath',
|
|
||||||
onProgress: (stream) {
|
|
||||||
stream.listen(
|
|
||||||
null,
|
|
||||||
onDone: () => completer.complete(null),
|
|
||||||
onError: (e) => completer.complete(
|
|
||||||
ICloudErr(type: ICloudErrType.generic, message: '$e'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Download $relativePath failed', e, s);
|
|
||||||
completer.complete(ICloudErr(type: ICloudErrType.generic, message: '$e'));
|
|
||||||
}
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sync file between iCloud and local
|
|
||||||
///
|
|
||||||
/// - [relativePaths] is the path relative to [Paths.doc],
|
|
||||||
/// must not starts with `/`
|
|
||||||
/// - [bakPrefix] is the suffix of backup file, default to [null].
|
|
||||||
/// All files downloaded from cloud will be suffixed with [bakPrefix].
|
|
||||||
///
|
|
||||||
/// Return `null` if upload success, [ICloudErr] otherwise
|
|
||||||
static Future<SyncResult<String, ICloudErr>> syncFiles({
|
|
||||||
required Iterable<String> relativePaths,
|
|
||||||
String? bakPrefix,
|
|
||||||
}) async {
|
|
||||||
final uploadFiles = <String>[];
|
|
||||||
final downloadFiles = <String>[];
|
|
||||||
|
|
||||||
try {
|
|
||||||
final errs = <String, ICloudErr>{};
|
|
||||||
|
|
||||||
final allFiles = await getAll();
|
|
||||||
|
|
||||||
/// remove files not in relativePaths
|
|
||||||
allFiles.removeWhere((e) => !relativePaths.contains(e.relativePath));
|
|
||||||
|
|
||||||
final missions = <Future<void>>[];
|
|
||||||
|
|
||||||
/// upload files not in iCloud
|
|
||||||
final missed = relativePaths.where((e) {
|
|
||||||
return !allFiles.any((f) => f.relativePath == e);
|
|
||||||
});
|
|
||||||
missions.addAll(missed.map((e) async {
|
|
||||||
final err = await upload(relativePath: e);
|
|
||||||
if (err != null) {
|
|
||||||
errs[e] = err;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
final docPath = Paths.doc;
|
|
||||||
|
|
||||||
/// compare files in iCloud and local
|
|
||||||
missions.addAll(allFiles.map((file) async {
|
|
||||||
final relativePath = file.relativePath;
|
|
||||||
|
|
||||||
/// Check date
|
|
||||||
final localFile = File('$docPath/$relativePath');
|
|
||||||
if (!localFile.existsSync()) {
|
|
||||||
/// Local file not found, download remote file
|
|
||||||
final err = await download(relativePath: relativePath);
|
|
||||||
if (err != null) {
|
|
||||||
errs[relativePath] = err;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final localDate = await localFile.lastModified();
|
|
||||||
final remoteDate = file.contentChangeDate;
|
|
||||||
|
|
||||||
/// Same date, skip
|
|
||||||
if (remoteDate.difference(localDate) == Duration.zero) return;
|
|
||||||
|
|
||||||
/// Local is newer than remote, so upload local file
|
|
||||||
if (remoteDate.isBefore(localDate)) {
|
|
||||||
await delete(relativePath);
|
|
||||||
final err = await upload(relativePath: relativePath);
|
|
||||||
if (err != null) {
|
|
||||||
errs[relativePath] = err;
|
|
||||||
}
|
|
||||||
uploadFiles.add(relativePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remote is newer than local, so download remote
|
|
||||||
final localPath = '$docPath/${bakPrefix ?? ''}$relativePath';
|
|
||||||
final err = await download(
|
|
||||||
relativePath: relativePath,
|
|
||||||
localPath: localPath,
|
|
||||||
);
|
|
||||||
if (err != null) {
|
|
||||||
errs[relativePath] = err;
|
|
||||||
}
|
|
||||||
downloadFiles.add(relativePath);
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Future.wait(missions);
|
|
||||||
|
|
||||||
return SyncResult(up: uploadFiles, down: downloadFiles, err: errs);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Sync: $relativePaths failed', e, s);
|
|
||||||
return SyncResult(up: uploadFiles, down: downloadFiles, err: {
|
|
||||||
'Generic': ICloudErr(type: ICloudErrType.generic, message: '$e')
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
_logger.info('Sync, up: $uploadFiles, down: $downloadFiles');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> sync() async {
|
|
||||||
final result = await download(relativePath: Paths.bakName);
|
|
||||||
if (result != null) {
|
|
||||||
_logger.warning('Download backup failed: $result');
|
|
||||||
await backup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final dlFile = await File(Paths.bakPath).readAsString();
|
|
||||||
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
|
|
||||||
await dlBak.restore();
|
|
||||||
|
|
||||||
await backup();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> backup() async {
|
|
||||||
await Backup.backup();
|
|
||||||
final uploadResult = await upload(relativePath: Paths.bakName);
|
|
||||||
if (uploadResult != null) {
|
|
||||||
_logger.warning('Upload backup failed: $uploadResult');
|
|
||||||
} else {
|
|
||||||
_logger.info('Upload backup success');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:computer/computer.dart';
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:toolbox/data/model/app/backup.dart';
|
|
||||||
import 'package:toolbox/data/model/app/error.dart';
|
|
||||||
import 'package:toolbox/data/res/store.dart';
|
|
||||||
import 'package:webdav_client/webdav_client.dart';
|
|
||||||
|
|
||||||
abstract final class Webdav {
|
|
||||||
/// Some WebDAV provider only support non-root path
|
|
||||||
static const _prefix = 'srvbox/';
|
|
||||||
|
|
||||||
static var _client = WebdavClient(
|
|
||||||
url: Stores.setting.webdavUrl.fetch(),
|
|
||||||
user: Stores.setting.webdavUser.fetch(),
|
|
||||||
pwd: Stores.setting.webdavPwd.fetch(),
|
|
||||||
);
|
|
||||||
|
|
||||||
static final _logger = Logger('Webdav');
|
|
||||||
|
|
||||||
static Future<String?> test(String url, String user, String pwd) async {
|
|
||||||
final client = WebdavClient(url: url, user: user, pwd: pwd);
|
|
||||||
try {
|
|
||||||
await client.ping();
|
|
||||||
return null;
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Test failed', e, s);
|
|
||||||
return e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<WebdavErr?> upload({
|
|
||||||
required String relativePath,
|
|
||||||
String? localPath,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
await _client.writeFile(
|
|
||||||
localPath ?? '${Paths.doc}/$relativePath',
|
|
||||||
_prefix + relativePath,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Upload $relativePath failed', e, s);
|
|
||||||
return WebdavErr(type: WebdavErrType.generic, message: '$e');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<WebdavErr?> delete(String relativePath) async {
|
|
||||||
try {
|
|
||||||
await _client.remove(_prefix + relativePath);
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('Delete $relativePath failed', e, s);
|
|
||||||
return WebdavErr(type: WebdavErrType.generic, message: '$e');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<WebdavErr?> download({
|
|
||||||
required String relativePath,
|
|
||||||
String? localPath,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
await _client.readFile(
|
|
||||||
_prefix + relativePath,
|
|
||||||
localPath ?? '${Paths.doc}/$relativePath',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
_logger.warning('Download $relativePath failed');
|
|
||||||
return WebdavErr(type: WebdavErrType.generic, message: '$e');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<String>> list() async {
|
|
||||||
try {
|
|
||||||
final list = await _client.readDir(_prefix);
|
|
||||||
final names = <String>[];
|
|
||||||
for (final item in list) {
|
|
||||||
if ((item.isDir ?? true) || item.name == null) continue;
|
|
||||||
names.add(item.name!);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.warning('List failed', e, s);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void changeClient(String url, String user, String pwd) {
|
|
||||||
_client = WebdavClient(url: url, user: user, pwd: pwd);
|
|
||||||
Stores.setting.webdavUrl.put(url);
|
|
||||||
Stores.setting.webdavUser.put(user);
|
|
||||||
Stores.setting.webdavPwd.put(pwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> sync() async {
|
|
||||||
final result = await download(relativePath: Paths.bakName);
|
|
||||||
if (result != null) {
|
|
||||||
_logger.warning('Download failed: $result');
|
|
||||||
await backup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final dlFile = await File(Paths.bakPath).readAsString();
|
|
||||||
final dlBak = await Computer.shared.start(Backup.fromJsonString, dlFile);
|
|
||||||
await dlBak.restore();
|
|
||||||
} catch (e) {
|
|
||||||
_logger.warning('Restore failed: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
await backup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a local backup and upload it to WebDAV
|
|
||||||
static Future<void> backup() async {
|
|
||||||
await Backup.backup();
|
|
||||||
final uploadResult = await upload(relativePath: Paths.bakName);
|
|
||||||
if (uploadResult != null) {
|
|
||||||
_logger.warning('Upload failed: $uploadResult');
|
|
||||||
} else {
|
|
||||||
_logger.info('Upload success');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,28 +2,33 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/data/model/server/snippet.dart';
|
import 'package:server_box/data/model/server/snippet.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
import 'package:toolbox/data/res/rebuild.dart';
|
import 'package:server_box/data/res/rebuild.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
|
part 'backup.g.dart';
|
||||||
|
|
||||||
const backupFormatVersion = 1;
|
const backupFormatVersion = 1;
|
||||||
|
|
||||||
final _logger = Logger('Backup');
|
final _logger = Logger('Backup');
|
||||||
|
|
||||||
class Backup {
|
@JsonSerializable()
|
||||||
|
class Backup implements Mergeable {
|
||||||
// backup format version
|
// backup format version
|
||||||
final int version;
|
final int version;
|
||||||
final String date;
|
final String date;
|
||||||
final List<ServerPrivateInfo> spis;
|
final List<Spi> spis;
|
||||||
final List<Snippet> snippets;
|
final List<Snippet> snippets;
|
||||||
final List<PrivateKeyInfo> keys;
|
final List<PrivateKeyInfo> keys;
|
||||||
final Map<String, dynamic> container;
|
final Map<String, dynamic> container;
|
||||||
final Map<String, dynamic> history;
|
final Map<String, dynamic> history;
|
||||||
final int? lastModTime;
|
final int? lastModTime;
|
||||||
|
final Map<String, dynamic>? settings;
|
||||||
|
|
||||||
const Backup({
|
const Backup({
|
||||||
required this.version,
|
required this.version,
|
||||||
@@ -33,34 +38,13 @@ class Backup {
|
|||||||
required this.keys,
|
required this.keys,
|
||||||
required this.container,
|
required this.container,
|
||||||
required this.history,
|
required this.history,
|
||||||
|
required this.settings,
|
||||||
this.lastModTime,
|
this.lastModTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
Backup.fromJson(Map<String, dynamic> json)
|
factory Backup.fromJson(Map<String, dynamic> json) => _$BackupFromJson(json);
|
||||||
: version = json['version'] as int,
|
|
||||||
date = json['date'],
|
|
||||||
spis = (json['spis'] as List)
|
|
||||||
.map((e) => ServerPrivateInfo.fromJson(e))
|
|
||||||
.toList(),
|
|
||||||
snippets =
|
|
||||||
(json['snippets'] as List).map((e) => Snippet.fromJson(e)).toList(),
|
|
||||||
keys = (json['keys'] as List)
|
|
||||||
.map((e) => PrivateKeyInfo.fromJson(e))
|
|
||||||
.toList(),
|
|
||||||
container = json['container'] ?? {},
|
|
||||||
lastModTime = json['lastModTime'],
|
|
||||||
history = json['history'] ?? {};
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => _$BackupToJson(this);
|
||||||
'version': version,
|
|
||||||
'date': date,
|
|
||||||
'spis': spis,
|
|
||||||
'snippets': snippets,
|
|
||||||
'keys': keys,
|
|
||||||
'container': container,
|
|
||||||
'lastModTime': lastModTime,
|
|
||||||
'history': history,
|
|
||||||
};
|
|
||||||
|
|
||||||
Backup.loadFromStore()
|
Backup.loadFromStore()
|
||||||
: version = backupFormatVersion,
|
: version = backupFormatVersion,
|
||||||
@@ -70,16 +54,18 @@ class Backup {
|
|||||||
keys = Stores.key.fetch(),
|
keys = Stores.key.fetch(),
|
||||||
container = Stores.container.box.toJson(),
|
container = Stores.container.box.toJson(),
|
||||||
lastModTime = Stores.lastModTime,
|
lastModTime = Stores.lastModTime,
|
||||||
history = Stores.history.box.toJson();
|
history = Stores.history.box.toJson(),
|
||||||
|
settings = Stores.setting.box.toJson();
|
||||||
|
|
||||||
static Future<String> backup([String? name]) async {
|
static Future<String> backup([String? name]) async {
|
||||||
final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson()));
|
final result = _diyEncrypt(json.encode(Backup.loadFromStore().toJson()));
|
||||||
final path = '${Paths.doc}/${name ?? 'srvbox_bak.json'}';
|
final path = Paths.doc.joinPath(name ?? Miscs.bakFileName);
|
||||||
await File(path).writeAsString(result);
|
await File(path).writeAsString(result);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restore({bool force = false}) async {
|
@override
|
||||||
|
Future<void> merge({bool force = false}) async {
|
||||||
final curTime = Stores.lastModTime ?? 0;
|
final curTime = Stores.lastModTime ?? 0;
|
||||||
final bakTime = lastModTime ?? 0;
|
final bakTime = lastModTime ?? 0;
|
||||||
final shouldRestore = force || curTime < bakTime;
|
final shouldRestore = force || curTime < bakTime;
|
||||||
@@ -89,93 +75,142 @@ class Backup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snippets
|
// Snippets
|
||||||
final nowSnippets = Stores.snippet.box.keys.toSet();
|
if (force) {
|
||||||
final bakSnippets = snippets.map((e) => e.name).toSet();
|
for (final s in snippets) {
|
||||||
final newSnippets = bakSnippets.difference(nowSnippets);
|
Stores.snippet.box.put(s.name, s);
|
||||||
final delSnippets = nowSnippets.difference(bakSnippets);
|
}
|
||||||
final updateSnippets = nowSnippets.intersection(bakSnippets);
|
} else {
|
||||||
for (final s in newSnippets) {
|
final nowSnippets = Stores.snippet.box.keys.toSet();
|
||||||
Stores.snippet.box.put(s, snippets.firstWhere((e) => e.name == s));
|
final bakSnippets = snippets.map((e) => e.name).toSet();
|
||||||
}
|
final newSnippets = bakSnippets.difference(nowSnippets);
|
||||||
for (final s in delSnippets) {
|
final delSnippets = nowSnippets.difference(bakSnippets);
|
||||||
Stores.snippet.box.delete(s);
|
final updateSnippets = nowSnippets.intersection(bakSnippets);
|
||||||
}
|
for (final s in newSnippets) {
|
||||||
for (final s in updateSnippets) {
|
Stores.snippet.box.put(s, snippets.firstWhere((e) => e.name == s));
|
||||||
Stores.snippet.box.put(s, snippets.firstWhere((e) => e.name == s));
|
}
|
||||||
|
for (final s in delSnippets) {
|
||||||
|
Stores.snippet.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateSnippets) {
|
||||||
|
Stores.snippet.box.put(s, snippets.firstWhere((e) => e.name == s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerPrivateInfo
|
// ServerPrivateInfo
|
||||||
final nowSpis = Stores.server.box.keys.toSet();
|
if (force) {
|
||||||
final bakSpis = spis.map((e) => e.id).toSet();
|
for (final s in spis) {
|
||||||
final newSpis = bakSpis.difference(nowSpis);
|
Stores.server.box.put(s.id, s);
|
||||||
final delSpis = nowSpis.difference(bakSpis);
|
}
|
||||||
final updateSpis = nowSpis.intersection(bakSpis);
|
} else {
|
||||||
for (final s in newSpis) {
|
final nowSpis = Stores.server.box.keys.toSet();
|
||||||
Stores.server.box.put(s, spis.firstWhere((e) => e.id == s));
|
final bakSpis = spis.map((e) => e.id).toSet();
|
||||||
}
|
final newSpis = bakSpis.difference(nowSpis);
|
||||||
for (final s in delSpis) {
|
final delSpis = nowSpis.difference(bakSpis);
|
||||||
Stores.server.box.delete(s);
|
final updateSpis = nowSpis.intersection(bakSpis);
|
||||||
}
|
for (final s in newSpis) {
|
||||||
for (final s in updateSpis) {
|
Stores.server.box.put(s, spis.firstWhere((e) => e.id == s));
|
||||||
Stores.server.box.put(s, spis.firstWhere((e) => e.id == s));
|
}
|
||||||
|
for (final s in delSpis) {
|
||||||
|
Stores.server.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateSpis) {
|
||||||
|
Stores.server.box.put(s, spis.firstWhere((e) => e.id == s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateKeyInfo
|
// PrivateKeyInfo
|
||||||
final nowKeys = Stores.key.box.keys.toSet();
|
if (force) {
|
||||||
final bakKeys = keys.map((e) => e.id).toSet();
|
for (final s in keys) {
|
||||||
final newKeys = bakKeys.difference(nowKeys);
|
Stores.key.box.put(s.id, s);
|
||||||
final delKeys = nowKeys.difference(bakKeys);
|
}
|
||||||
final updateKeys = nowKeys.intersection(bakKeys);
|
} else {
|
||||||
for (final s in newKeys) {
|
final nowKeys = Stores.key.box.keys.toSet();
|
||||||
Stores.key.box.put(s, keys.firstWhere((e) => e.id == s));
|
final bakKeys = keys.map((e) => e.id).toSet();
|
||||||
}
|
final newKeys = bakKeys.difference(nowKeys);
|
||||||
for (final s in delKeys) {
|
final delKeys = nowKeys.difference(bakKeys);
|
||||||
Stores.key.box.delete(s);
|
final updateKeys = nowKeys.intersection(bakKeys);
|
||||||
}
|
for (final s in newKeys) {
|
||||||
for (final s in updateKeys) {
|
Stores.key.box.put(s, keys.firstWhere((e) => e.id == s));
|
||||||
Stores.key.box.put(s, keys.firstWhere((e) => e.id == s));
|
}
|
||||||
|
for (final s in delKeys) {
|
||||||
|
Stores.key.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateKeys) {
|
||||||
|
Stores.key.box.put(s, keys.firstWhere((e) => e.id == s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// History
|
// History
|
||||||
final nowHistory = Stores.history.box.keys.toSet();
|
if (force) {
|
||||||
final bakHistory = history.keys.toSet();
|
Stores.history.box.putAll(history);
|
||||||
final newHistory = bakHistory.difference(nowHistory);
|
} else {
|
||||||
final delHistory = nowHistory.difference(bakHistory);
|
final nowHistory = Stores.history.box.keys.toSet();
|
||||||
final updateHistory = nowHistory.intersection(bakHistory);
|
final bakHistory = history.keys.toSet();
|
||||||
for (final s in newHistory) {
|
final newHistory = bakHistory.difference(nowHistory);
|
||||||
Stores.history.box.put(s, history[s]);
|
final delHistory = nowHistory.difference(bakHistory);
|
||||||
}
|
final updateHistory = nowHistory.intersection(bakHistory);
|
||||||
for (final s in delHistory) {
|
for (final s in newHistory) {
|
||||||
Stores.history.box.delete(s);
|
Stores.history.box.put(s, history[s]);
|
||||||
}
|
}
|
||||||
for (final s in updateHistory) {
|
for (final s in delHistory) {
|
||||||
Stores.history.box.put(s, history[s]);
|
Stores.history.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateHistory) {
|
||||||
|
Stores.history.box.put(s, history[s]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container
|
// Container
|
||||||
final nowContainer = Stores.container.box.keys.toSet();
|
if (force) {
|
||||||
final bakContainer = container.keys.toSet();
|
Stores.container.box.putAll(container);
|
||||||
final newContainer = bakContainer.difference(nowContainer);
|
} else {
|
||||||
final delContainer = nowContainer.difference(bakContainer);
|
final nowContainer = Stores.container.box.keys.toSet();
|
||||||
final updateContainer = nowContainer.intersection(bakContainer);
|
final bakContainer = container.keys.toSet();
|
||||||
for (final s in newContainer) {
|
final newContainer = bakContainer.difference(nowContainer);
|
||||||
Stores.container.box.put(s, container[s]);
|
final delContainer = nowContainer.difference(bakContainer);
|
||||||
}
|
final updateContainer = nowContainer.intersection(bakContainer);
|
||||||
for (final s in delContainer) {
|
for (final s in newContainer) {
|
||||||
Stores.container.box.delete(s);
|
Stores.container.box.put(s, container[s]);
|
||||||
}
|
}
|
||||||
for (final s in updateContainer) {
|
for (final s in delContainer) {
|
||||||
Stores.container.box.put(s, container[s]);
|
Stores.container.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateContainer) {
|
||||||
|
Stores.container.box.put(s, container[s]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Pros.reload();
|
// Settings
|
||||||
RebuildNodes.app.rebuild();
|
final settings_ = settings;
|
||||||
|
if (settings_ != null) {
|
||||||
|
if (force) {
|
||||||
|
Stores.setting.box.putAll(settings_);
|
||||||
|
} else {
|
||||||
|
final nowSettings = Stores.setting.box.keys.toSet();
|
||||||
|
final bakSettings = settings_.keys.toSet();
|
||||||
|
final newSettings = bakSettings.difference(nowSettings);
|
||||||
|
final delSettings = nowSettings.difference(bakSettings);
|
||||||
|
final updateSettings = nowSettings.intersection(bakSettings);
|
||||||
|
for (final s in newSettings) {
|
||||||
|
Stores.setting.box.put(s, settings_[s]);
|
||||||
|
}
|
||||||
|
for (final s in delSettings) {
|
||||||
|
Stores.setting.box.delete(s);
|
||||||
|
}
|
||||||
|
for (final s in updateSettings) {
|
||||||
|
Stores.setting.box.put(s, settings_[s]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider.reload();
|
||||||
|
RNodes.app.notify();
|
||||||
|
|
||||||
_logger.info('Restore success');
|
_logger.info('Restore success');
|
||||||
}
|
}
|
||||||
|
|
||||||
Backup.fromJsonString(String raw)
|
factory Backup.fromJsonString(String raw) =>
|
||||||
: this.fromJson(json.decode(_diyDecrypt(raw)));
|
Backup.fromJson(json.decode(_diyDecrypt(raw)));
|
||||||
}
|
}
|
||||||
|
|
||||||
String _diyEncrypt(String raw) => json.encode(
|
String _diyEncrypt(String raw) => json.encode(
|
||||||
|
|||||||
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,4 +1,4 @@
|
|||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
|
||||||
enum ErrFrom {
|
enum ErrFrom {
|
||||||
unknown,
|
unknown,
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
typedef GhId = String;
|
|
||||||
|
|
||||||
extension GhIdX on GhId {
|
|
||||||
String get url => 'https://github.com/$this';
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
|
||||||
enum ContainerMenu {
|
enum ContainerMenu {
|
||||||
start,
|
start,
|
||||||
@@ -21,46 +22,27 @@ enum ContainerMenu {
|
|||||||
terminal,
|
terminal,
|
||||||
//stats,
|
//stats,
|
||||||
];
|
];
|
||||||
} else {
|
|
||||||
return [start, rm, logs];
|
|
||||||
}
|
}
|
||||||
|
return [start, rm, logs];
|
||||||
}
|
}
|
||||||
|
|
||||||
IconData get icon {
|
IconData get icon => switch (this) {
|
||||||
switch (this) {
|
ContainerMenu.start => Icons.play_arrow,
|
||||||
case ContainerMenu.start:
|
ContainerMenu.stop => Icons.stop,
|
||||||
return Icons.play_arrow;
|
ContainerMenu.restart => Icons.restart_alt,
|
||||||
case ContainerMenu.stop:
|
ContainerMenu.rm => Icons.delete,
|
||||||
return Icons.stop;
|
ContainerMenu.logs => Icons.logo_dev,
|
||||||
case ContainerMenu.restart:
|
ContainerMenu.terminal => Icons.terminal,
|
||||||
return Icons.restart_alt;
|
// DockerMenuType.stats => Icons.bar_chart,
|
||||||
case ContainerMenu.rm:
|
};
|
||||||
return Icons.delete;
|
|
||||||
case ContainerMenu.logs:
|
|
||||||
return Icons.logo_dev;
|
|
||||||
case ContainerMenu.terminal:
|
|
||||||
return Icons.terminal;
|
|
||||||
// case DockerMenuType.stats:
|
|
||||||
// return Icons.bar_chart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get toStr {
|
String get toStr => switch (this) {
|
||||||
switch (this) {
|
ContainerMenu.start => l10n.start,
|
||||||
case ContainerMenu.start:
|
ContainerMenu.stop => l10n.stop,
|
||||||
return l10n.start;
|
ContainerMenu.restart => l10n.restart,
|
||||||
case ContainerMenu.stop:
|
ContainerMenu.rm => libL10n.delete,
|
||||||
return l10n.stop;
|
ContainerMenu.logs => libL10n.log,
|
||||||
case ContainerMenu.restart:
|
ContainerMenu.terminal => l10n.terminal,
|
||||||
return l10n.restart;
|
// DockerMenuType.stats => s.stats,
|
||||||
case ContainerMenu.rm:
|
};
|
||||||
return l10n.delete;
|
|
||||||
case ContainerMenu.logs:
|
|
||||||
return l10n.log;
|
|
||||||
case ContainerMenu.terminal:
|
|
||||||
return l10n.terminal;
|
|
||||||
// case DockerMenuType.stats:
|
|
||||||
// return s.stats;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,77 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
part 'server_func.g.dart';
|
part 'server_func.g.dart';
|
||||||
|
|
||||||
@HiveType(typeId: 6)
|
@HiveType(typeId: 6)
|
||||||
enum ServerFuncBtn {
|
enum ServerFuncBtn {
|
||||||
@HiveField(0)
|
@HiveField(0)
|
||||||
terminal,
|
terminal._(),
|
||||||
@HiveField(1)
|
@HiveField(1)
|
||||||
sftp,
|
sftp._(),
|
||||||
@HiveField(2)
|
@HiveField(2)
|
||||||
container,
|
container._(),
|
||||||
@HiveField(3)
|
@HiveField(3)
|
||||||
process,
|
process._(),
|
||||||
@HiveField(4)
|
//@HiveField(4)
|
||||||
pkg,
|
//pkg,
|
||||||
@HiveField(5)
|
@HiveField(5)
|
||||||
snippet,
|
snippet._(),
|
||||||
@HiveField(6)
|
@HiveField(6)
|
||||||
iperf,
|
iperf._(),
|
||||||
// @HiveField(7)
|
// @HiveField(7)
|
||||||
// pve,
|
// pve,
|
||||||
|
@HiveField(8)
|
||||||
|
systemd._(1058),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
final int? addedVersion;
|
||||||
|
|
||||||
|
const ServerFuncBtn._([this.addedVersion]);
|
||||||
|
|
||||||
|
static void autoAddNewFuncs(int cur) {
|
||||||
|
if (cur >= systemd.addedVersion!) {
|
||||||
|
final prop = Stores.setting.serverFuncBtns;
|
||||||
|
final list = prop.fetch();
|
||||||
|
if (!list.contains(systemd.index)) {
|
||||||
|
list.add(systemd.index);
|
||||||
|
prop.put(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static final defaultIdxs = [
|
static final defaultIdxs = [
|
||||||
terminal,
|
terminal,
|
||||||
sftp,
|
sftp,
|
||||||
container,
|
container,
|
||||||
process,
|
process,
|
||||||
pkg,
|
//pkg,
|
||||||
snippet,
|
snippet,
|
||||||
|
systemd,
|
||||||
].map((e) => e.index).toList();
|
].map((e) => e.index).toList();
|
||||||
|
|
||||||
IconData get icon => switch (this) {
|
IconData get icon => switch (this) {
|
||||||
sftp => Icons.insert_drive_file,
|
sftp => Icons.insert_drive_file,
|
||||||
snippet => Icons.code,
|
snippet => Icons.code,
|
||||||
pkg => Icons.system_security_update,
|
//pkg => Icons.system_security_update,
|
||||||
container => FontAwesome.docker_brand,
|
container => FontAwesome.docker_brand,
|
||||||
process => Icons.list_alt_outlined,
|
process => Icons.list_alt_outlined,
|
||||||
terminal => Icons.terminal,
|
terminal => Icons.terminal,
|
||||||
iperf => Icons.speed,
|
iperf => Icons.speed,
|
||||||
|
systemd => MingCute.plugin_2_fill,
|
||||||
};
|
};
|
||||||
|
|
||||||
String get toStr => switch (this) {
|
String get toStr => switch (this) {
|
||||||
sftp => 'SFTP',
|
sftp => 'SFTP',
|
||||||
snippet => l10n.snippet,
|
snippet => l10n.snippet,
|
||||||
pkg => l10n.pkg,
|
//pkg => l10n.pkg,
|
||||||
container => l10n.container,
|
container => l10n.container,
|
||||||
process => l10n.process,
|
process => l10n.process,
|
||||||
terminal => l10n.terminal,
|
terminal => l10n.terminal,
|
||||||
iperf => 'iperf',
|
iperf => 'iperf',
|
||||||
|
systemd => 'Systemd',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
|||||||
return ServerFuncBtn.container;
|
return ServerFuncBtn.container;
|
||||||
case 3:
|
case 3:
|
||||||
return ServerFuncBtn.process;
|
return ServerFuncBtn.process;
|
||||||
case 4:
|
|
||||||
return ServerFuncBtn.pkg;
|
|
||||||
case 5:
|
case 5:
|
||||||
return ServerFuncBtn.snippet;
|
return ServerFuncBtn.snippet;
|
||||||
case 6:
|
case 6:
|
||||||
return ServerFuncBtn.iperf;
|
return ServerFuncBtn.iperf;
|
||||||
|
case 8:
|
||||||
|
return ServerFuncBtn.systemd;
|
||||||
default:
|
default:
|
||||||
return ServerFuncBtn.terminal;
|
return ServerFuncBtn.terminal;
|
||||||
}
|
}
|
||||||
@@ -47,15 +47,15 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
|||||||
case ServerFuncBtn.process:
|
case ServerFuncBtn.process:
|
||||||
writer.writeByte(3);
|
writer.writeByte(3);
|
||||||
break;
|
break;
|
||||||
case ServerFuncBtn.pkg:
|
|
||||||
writer.writeByte(4);
|
|
||||||
break;
|
|
||||||
case ServerFuncBtn.snippet:
|
case ServerFuncBtn.snippet:
|
||||||
writer.writeByte(5);
|
writer.writeByte(5);
|
||||||
break;
|
break;
|
||||||
case ServerFuncBtn.iperf:
|
case ServerFuncBtn.iperf:
|
||||||
writer.writeByte(6);
|
writer.writeByte(6);
|
||||||
break;
|
break;
|
||||||
|
case ServerFuncBtn.systemd:
|
||||||
|
writer.writeByte(8);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
|
||||||
|
|
||||||
part 'net_view.g.dart';
|
part 'net_view.g.dart';
|
||||||
|
|
||||||
@@ -14,80 +14,67 @@ enum NetViewType {
|
|||||||
@HiveField(2)
|
@HiveField(2)
|
||||||
traffic;
|
traffic;
|
||||||
|
|
||||||
NetViewType get next {
|
NetViewType get next => switch (this) {
|
||||||
switch (this) {
|
conn => speed,
|
||||||
case conn:
|
speed => traffic,
|
||||||
return speed;
|
traffic => conn,
|
||||||
case speed:
|
};
|
||||||
return traffic;
|
|
||||||
case traffic:
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get toStr {
|
String get toStr => switch (this) {
|
||||||
switch (this) {
|
NetViewType.conn => l10n.conn,
|
||||||
case NetViewType.conn:
|
NetViewType.traffic => l10n.traffic,
|
||||||
return l10n.conn;
|
NetViewType.speed => l10n.speed,
|
||||||
case NetViewType.traffic:
|
};
|
||||||
return l10n.traffic;
|
|
||||||
case NetViewType.speed:
|
|
||||||
return l10n.speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(String, String) build(ServerStatus ss) {
|
/// If no device is specified, return the cached value (only real devices,
|
||||||
final ignoreLocal = Stores.setting.ignoreLocalNet.fetch();
|
/// such as ethX, wlanX...).
|
||||||
switch (this) {
|
(String, String) build(ServerStatus ss, {String? dev}) {
|
||||||
case NetViewType.conn:
|
final notSepcifyDev = dev == null || dev.isEmpty;
|
||||||
return (
|
try {
|
||||||
'${l10n.conn}:\n${ss.tcp.maxConn}',
|
switch (this) {
|
||||||
'${l10n.failed}:\n${ss.tcp.fail}',
|
case NetViewType.conn:
|
||||||
);
|
|
||||||
case NetViewType.speed:
|
|
||||||
if (ignoreLocal) {
|
|
||||||
return (
|
return (
|
||||||
'↓:\n${ss.netSpeed.cachedRealVals.speedIn}',
|
'${l10n.conn}:\n${ss.tcp.maxConn}',
|
||||||
'↑:\n${ss.netSpeed.cachedRealVals.speedOut}',
|
'${libL10n.fail}:\n${ss.tcp.fail}',
|
||||||
);
|
);
|
||||||
}
|
case NetViewType.speed:
|
||||||
return (
|
if (notSepcifyDev) {
|
||||||
'↓:\n${ss.netSpeed.speedIn()}',
|
return (
|
||||||
'↑:\n${ss.netSpeed.speedOut()}',
|
'↓:\n${ss.netSpeed.cachedVals.speedIn}',
|
||||||
);
|
'↑:\n${ss.netSpeed.cachedVals.speedOut}',
|
||||||
case NetViewType.traffic:
|
);
|
||||||
if (ignoreLocal) {
|
}
|
||||||
return (
|
return (
|
||||||
'↓:\n${ss.netSpeed.cachedRealVals.sizeIn}',
|
'↓:\n${ss.netSpeed.speedIn(device: dev)}',
|
||||||
'↑:\n${ss.netSpeed.cachedRealVals.sizeOut}',
|
'↑:\n${ss.netSpeed.speedOut(device: dev)}',
|
||||||
);
|
);
|
||||||
}
|
case NetViewType.traffic:
|
||||||
return (
|
if (notSepcifyDev) {
|
||||||
'↓:\n${ss.netSpeed.sizeIn()}',
|
return (
|
||||||
'↑:\n${ss.netSpeed.sizeOut()}',
|
'↓:\n${ss.netSpeed.cachedVals.sizeIn}',
|
||||||
);
|
'↑:\n${ss.netSpeed.cachedVals.sizeOut}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
'↓:\n${ss.netSpeed.sizeIn(device: dev)}',
|
||||||
|
'↑:\n${ss.netSpeed.sizeOut(device: dev)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('NetViewType.build', e, s);
|
||||||
|
return ('N/A', 'N/A');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int toJson() {
|
int toJson() => switch (this) {
|
||||||
switch (this) {
|
NetViewType.conn => 0,
|
||||||
case NetViewType.conn:
|
NetViewType.speed => 1,
|
||||||
return 0;
|
NetViewType.traffic => 2,
|
||||||
case NetViewType.speed:
|
};
|
||||||
return 1;
|
|
||||||
case NetViewType.traffic:
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static NetViewType fromJson(int json) {
|
static NetViewType fromJson(int json) => switch (json) {
|
||||||
switch (json) {
|
0 => NetViewType.conn,
|
||||||
case 0:
|
1 => NetViewType.speed,
|
||||||
return NetViewType.conn;
|
_ => NetViewType.traffic,
|
||||||
case 2:
|
};
|
||||||
return NetViewType.traffic;
|
|
||||||
default:
|
|
||||||
return NetViewType.speed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
|
||||||
|
final _seperator = Pfs.seperator;
|
||||||
|
|
||||||
/// It's used on platform's file system.
|
/// It's used on platform's file system.
|
||||||
/// So use [Platform.pathSeparator] to join path.
|
/// So use [Platform.pathSeparator] to join path.
|
||||||
class LocalPath {
|
class LocalPath {
|
||||||
final String _prefixPath;
|
final String _prefixPath;
|
||||||
String _path = '/';
|
String _path = _seperator;
|
||||||
String? _prePath;
|
String? _prePath;
|
||||||
String get path => _prefixPath + _path;
|
String get path => _prefixPath + _path;
|
||||||
|
|
||||||
@@ -13,20 +15,20 @@ class LocalPath {
|
|||||||
void update(String newPath) {
|
void update(String newPath) {
|
||||||
_prePath = _path;
|
_prePath = _path;
|
||||||
if (newPath == '..') {
|
if (newPath == '..') {
|
||||||
_path = _path.substring(0, _path.lastIndexOf('/'));
|
_path = _path.substring(0, _path.lastIndexOf(_seperator));
|
||||||
if (_path == '') {
|
if (_path == '') {
|
||||||
_path = '/';
|
_path = _seperator;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newPath == '/') {
|
if (newPath == _seperator) {
|
||||||
_path = '/';
|
_path = _seperator;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_path = _path.joinPath(newPath);
|
_path = _path.joinPath(newPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get canBack => path != '$_prefixPath/';
|
bool get canBack => path != '$_prefixPath$_seperator';
|
||||||
|
|
||||||
bool undo() {
|
bool undo() {
|
||||||
if (_prePath == null || _path == _prePath) {
|
if (_prePath == null || _path == _prePath) {
|
||||||
@@ -38,7 +40,7 @@ class LocalPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _trimSuffix(String prefixPath) {
|
String _trimSuffix(String prefixPath) {
|
||||||
if (prefixPath.endsWith('/')) {
|
if (prefixPath.endsWith(_seperator)) {
|
||||||
return prefixPath.substring(0, prefixPath.length - 1);
|
return prefixPath.substring(0, prefixPath.length - 1);
|
||||||
}
|
}
|
||||||
return prefixPath;
|
return prefixPath;
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class RebuildNode implements Listenable {
|
|
||||||
final List<VoidCallback> _listeners = [];
|
|
||||||
|
|
||||||
RebuildNode();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addListener(VoidCallback listener) {
|
|
||||||
_listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void removeListener(VoidCallback listener) {
|
|
||||||
_listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rebuild() {
|
|
||||||
for (var listener in _listeners) {
|
|
||||||
listener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
enum ServerDetailCards {
|
enum ServerDetailCards {
|
||||||
about(Icons.info),
|
about(Icons.info),
|
||||||
@@ -31,7 +31,7 @@ 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 => l10n.about,
|
about => libL10n.about,
|
||||||
cpu => 'CPU',
|
cpu => 'CPU',
|
||||||
mem => 'RAM',
|
mem => 'RAM',
|
||||||
swap => 'Swap',
|
swap => 'Swap',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/provider/server.dart';
|
||||||
|
|
||||||
import '../../res/build_data.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
import '../server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
enum ShellFunc {
|
enum ShellFunc {
|
||||||
status,
|
status,
|
||||||
@@ -12,52 +13,61 @@ enum ShellFunc {
|
|||||||
suspend,
|
suspend,
|
||||||
;
|
;
|
||||||
|
|
||||||
static const _homeVar = '\$HOME';
|
|
||||||
static const seperator = 'SrvBoxSep';
|
static const seperator = 'SrvBoxSep';
|
||||||
|
|
||||||
/// The suffix `\t` is for formatting
|
/// The suffix `\t` is for formatting
|
||||||
static const cmdDivider = '\necho $seperator\n\t';
|
static const cmdDivider = '\necho $seperator\n\t';
|
||||||
static const _srvBoxDir = '.config/server_box';
|
|
||||||
static const scriptFile = 'mobile_v${BuildData.script}.sh';
|
|
||||||
|
|
||||||
/// Issue #159
|
/// srvboxm -> ServerBox Mobile
|
||||||
|
static const scriptFile = 'srvboxm_v${BuildData.script}.sh';
|
||||||
|
static const scriptDirHome = '~/.config/server_box';
|
||||||
|
static const scriptDirTmp = '/tmp/server_box';
|
||||||
|
|
||||||
|
static final _scriptDirMap = <String, String>{};
|
||||||
|
|
||||||
|
/// Get the script directory for the given [id].
|
||||||
///
|
///
|
||||||
/// Use script commit count as version of shell script.
|
/// Default is [scriptDirTmp]/[scriptFile], if this path is not accessible,
|
||||||
///
|
/// it will be changed to [scriptDirHome]/[scriptFile].
|
||||||
/// So different version of app can run at the same time.
|
static String getScriptDir(String id) {
|
||||||
///
|
final customScriptDir =
|
||||||
/// **Can't** use it in SFTP, because SFTP can't recognize `$HOME`
|
ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||||
static String getShellPath(String home) => '$home/$_srvBoxDir/$scriptFile';
|
if (customScriptDir != null) return customScriptDir;
|
||||||
|
return _scriptDirMap.putIfAbsent(id, () {
|
||||||
static const srvBoxDir = '$_homeVar/$_srvBoxDir';
|
return scriptDirTmp;
|
||||||
static const _installShellPath = '$_homeVar/$_srvBoxDir/$scriptFile';
|
});
|
||||||
|
|
||||||
// Issue #299, chmod ~/.config to avoid permission issue
|
|
||||||
static const installShellCmd = """
|
|
||||||
chmod +x ~/.config &> /dev/null
|
|
||||||
mkdir -p $_homeVar/$_srvBoxDir
|
|
||||||
cat > $_installShellPath
|
|
||||||
chmod +x $_installShellPath
|
|
||||||
""";
|
|
||||||
|
|
||||||
String get flag {
|
|
||||||
switch (this) {
|
|
||||||
case ShellFunc.status:
|
|
||||||
return 's';
|
|
||||||
// case ShellFunc.docker:
|
|
||||||
// return 'd';
|
|
||||||
case ShellFunc.process:
|
|
||||||
return 'p';
|
|
||||||
case ShellFunc.shutdown:
|
|
||||||
return 'sd';
|
|
||||||
case ShellFunc.reboot:
|
|
||||||
return 'r';
|
|
||||||
case ShellFunc.suspend:
|
|
||||||
return 'sp';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String get exec => 'sh $_installShellPath -$flag';
|
static void switchScriptDir(String id) => switch (_scriptDirMap[id]) {
|
||||||
|
scriptDirTmp => _scriptDirMap[id] = scriptDirHome,
|
||||||
|
scriptDirHome => _scriptDirMap[id] = scriptDirTmp,
|
||||||
|
_ => _scriptDirMap[id] = scriptDirHome,
|
||||||
|
};
|
||||||
|
|
||||||
|
static String getScriptPath(String id) {
|
||||||
|
return '${getScriptDir(id)}/$scriptFile';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getInstallShellCmd(String id) {
|
||||||
|
final scriptDir = getScriptDir(id);
|
||||||
|
final scriptPath = '$scriptDir/$scriptFile';
|
||||||
|
return '''
|
||||||
|
mkdir -p $scriptDir
|
||||||
|
cat > $scriptPath
|
||||||
|
chmod 755 $scriptPath
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get flag => switch (this) {
|
||||||
|
ShellFunc.process => 'p',
|
||||||
|
ShellFunc.shutdown => 'sd',
|
||||||
|
ShellFunc.reboot => 'r',
|
||||||
|
ShellFunc.suspend => 'sp',
|
||||||
|
ShellFunc.status => 's',
|
||||||
|
// ShellFunc.docker=> 'd',
|
||||||
|
};
|
||||||
|
|
||||||
|
String exec(String id) => 'sh ${getScriptPath(id)} -$flag';
|
||||||
|
|
||||||
String get name {
|
String get name {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
@@ -213,6 +223,7 @@ enum StatusCmdType {
|
|||||||
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done'),
|
||||||
nvidia._('nvidia-smi -q -x'),
|
nvidia._('nvidia-smi -q -x'),
|
||||||
sensors._('sensors'),
|
sensors._('sensors'),
|
||||||
|
cpuBrand._('cat /proc/cpuinfo | grep "model name"'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String cmd;
|
final String cmd;
|
||||||
@@ -231,6 +242,7 @@ enum BSDStatusCmdType {
|
|||||||
mem._('top -l 1 | grep PhysMem'),
|
mem._('top -l 1 | grep PhysMem'),
|
||||||
//temp,
|
//temp,
|
||||||
host._('hostname'),
|
host._('hostname'),
|
||||||
|
cpuBrand._('sysctl -n machdep.cpu.brand_string'),
|
||||||
;
|
;
|
||||||
|
|
||||||
final String cmd;
|
final String cmd;
|
||||||
|
|||||||
@@ -1,26 +1,62 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/view/page/ping.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/view/page/server/tab.dart';
|
import 'package:server_box/view/page/server/tab.dart';
|
||||||
import 'package:toolbox/view/page/snippet/list.dart';
|
import 'package:server_box/view/page/setting/entry.dart';
|
||||||
import 'package:toolbox/view/page/ssh/tab.dart';
|
import 'package:server_box/view/page/snippet/list.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';
|
||||||
|
|
||||||
enum AppTab {
|
enum AppTab {
|
||||||
server,
|
server,
|
||||||
ssh,
|
ssh,
|
||||||
|
file,
|
||||||
snippet,
|
snippet,
|
||||||
ping,
|
settings,
|
||||||
;
|
;
|
||||||
|
|
||||||
Widget get page {
|
Widget get page {
|
||||||
switch (this) {
|
return switch (this) {
|
||||||
case server:
|
server => const ServerPage(),
|
||||||
return const ServerPage();
|
settings => const SettingsPage(),
|
||||||
case snippet:
|
ssh => const SSHTabPage(),
|
||||||
return const SnippetListPage();
|
file => const LocalFilePage(),
|
||||||
case ssh:
|
snippet => const SnippetListPage(),
|
||||||
return const SSHTabPage();
|
};
|
||||||
case ping:
|
}
|
||||||
return const PingPage();
|
|
||||||
}
|
NavigationDestination get navDestination {
|
||||||
|
return switch (this) {
|
||||||
|
server => NavigationDestination(
|
||||||
|
icon: const Icon(BoxIcons.bx_server),
|
||||||
|
label: l10n.server,
|
||||||
|
selectedIcon: const Icon(BoxIcons.bxs_server),
|
||||||
|
),
|
||||||
|
settings => NavigationDestination(
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
label: libL10n.setting,
|
||||||
|
selectedIcon: const Icon(Icons.settings),
|
||||||
|
),
|
||||||
|
ssh => const NavigationDestination(
|
||||||
|
icon: Icon(Icons.terminal_outlined),
|
||||||
|
label: 'SSH',
|
||||||
|
selectedIcon: Icon(Icons.terminal),
|
||||||
|
),
|
||||||
|
snippet => NavigationDestination(
|
||||||
|
icon: const Icon(Icons.code),
|
||||||
|
label: l10n.snippet,
|
||||||
|
selectedIcon: const Icon(Icons.code),
|
||||||
|
),
|
||||||
|
file => NavigationDestination(
|
||||||
|
icon: const Icon(Icons.folder_open),
|
||||||
|
label: libL10n.file,
|
||||||
|
selectedIcon: const Icon(Icons.folder),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<NavigationDestination> get navDestinations {
|
||||||
|
return AppTab.values.map((e) => e.navDestination).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
abstract class TagPickable {
|
|
||||||
bool containsTag(String tag);
|
|
||||||
|
|
||||||
String get tagName;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:toolbox/data/model/container/type.dart';
|
import 'package:server_box/data/model/container/type.dart';
|
||||||
|
|
||||||
abstract final class ContainerImg {
|
abstract final class ContainerImg {
|
||||||
final String? repository = null;
|
final String? repository = null;
|
||||||
@@ -45,21 +45,21 @@ final class PodmanImg implements ContainerImg {
|
|||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
|
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
|
||||||
repository: json["repository"],
|
repository: json['repository'],
|
||||||
tag: json["tag"],
|
tag: json['tag'],
|
||||||
id: json["Id"],
|
id: json['Id'],
|
||||||
created: json["Created"],
|
created: json['Created'],
|
||||||
size: json["Size"],
|
size: json['Size'],
|
||||||
containers: json["Containers"],
|
containers: json['Containers'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"repository": repository,
|
'repository': repository,
|
||||||
"tag": tag,
|
'tag': tag,
|
||||||
"Id": id,
|
'Id': id,
|
||||||
"Created": created,
|
'Created': created,
|
||||||
"Size": size,
|
'Size': size,
|
||||||
"Containers": containers,
|
'Containers': containers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ final class DockerImg implements ContainerImg {
|
|||||||
final String repository;
|
final String repository;
|
||||||
final String size;
|
final String size;
|
||||||
@override
|
@override
|
||||||
final String tag;
|
final String? tag;
|
||||||
|
|
||||||
DockerImg({
|
DockerImg({
|
||||||
required this.containers,
|
required this.containers,
|
||||||
@@ -95,21 +95,37 @@ final class DockerImg implements ContainerImg {
|
|||||||
|
|
||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory DockerImg.fromJson(Map<String, dynamic> json) => DockerImg(
|
factory DockerImg.fromJson(Map<String, dynamic> json) {
|
||||||
containers: json["Containers"],
|
final containers = switch (json['Containers']) {
|
||||||
createdAt: json["CreatedAt"],
|
final String a => a,
|
||||||
id: json["ID"],
|
final Object? a => a.toString(),
|
||||||
repository: json["Repository"],
|
};
|
||||||
size: json["Size"],
|
final repo = switch (json['Repository'] ?? json['Names']) {
|
||||||
tag: json["Tag"],
|
final String a => a,
|
||||||
);
|
final List a => a.firstOrNull.toString(),
|
||||||
|
final Object? a => a.toString(),
|
||||||
|
};
|
||||||
|
final size = switch (json['Size']) {
|
||||||
|
final String a => a,
|
||||||
|
final int a => a.bytes2Str,
|
||||||
|
final Object? a => a.toString(),
|
||||||
|
};
|
||||||
|
return DockerImg(
|
||||||
|
containers: containers,
|
||||||
|
createdAt: json['CreatedAt'],
|
||||||
|
id: json['ID'] ?? json['Id'] ?? '',
|
||||||
|
repository: repo,
|
||||||
|
size: size,
|
||||||
|
tag: json['Tag'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"Containers": containers,
|
'Containers': containers,
|
||||||
"CreatedAt": createdAt,
|
'CreatedAt': createdAt,
|
||||||
"ID": id,
|
'ID': id,
|
||||||
"Repository": repository,
|
'Repository': repository,
|
||||||
"Size": size,
|
'Size': size,
|
||||||
"Tag": tag,
|
'Tag': tag,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/data/model/container/type.dart';
|
import 'package:server_box/data/model/container/type.dart';
|
||||||
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
abstract final class ContainerPs {
|
sealed class ContainerPs {
|
||||||
final String? id = null;
|
final String? id = null;
|
||||||
final String? image = null;
|
final String? image = null;
|
||||||
String? get name;
|
String? get name;
|
||||||
@@ -16,7 +17,7 @@ abstract final class ContainerPs {
|
|||||||
String? net;
|
String? net;
|
||||||
String? disk;
|
String? disk;
|
||||||
|
|
||||||
factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s);
|
factory ContainerPs.fromRaw(String s, ContainerType typ) => typ.ps(s);
|
||||||
|
|
||||||
void parseStats(String s);
|
void parseStats(String s);
|
||||||
}
|
}
|
||||||
@@ -83,35 +84,33 @@ final class PodmanPs implements ContainerPs {
|
|||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
factory PodmanPs.fromJson(Map<String, dynamic> json) => PodmanPs(
|
factory PodmanPs.fromJson(Map<String, dynamic> json) => PodmanPs(
|
||||||
command: json["Command"] == null
|
command: json['Command'] == null
|
||||||
? []
|
? []
|
||||||
: List<String>.from(json["Command"]!.map((x) => x)),
|
: List<String>.from(json['Command']!.map((x) => x)),
|
||||||
created:
|
created:
|
||||||
json["Created"] == null ? null : DateTime.parse(json["Created"]),
|
json['Created'] == null ? null : DateTime.parse(json['Created']),
|
||||||
exited: json["Exited"],
|
exited: json['Exited'],
|
||||||
id: json["Id"],
|
id: json['Id'],
|
||||||
image: json["Image"],
|
image: json['Image'],
|
||||||
names: json["Names"] == null
|
names: json['Names'] == null
|
||||||
? []
|
? []
|
||||||
: List<String>.from(json["Names"]!.map((x) => x)),
|
: List<String>.from(json['Names']!.map((x) => x)),
|
||||||
startedAt: json["StartedAt"],
|
startedAt: json['StartedAt'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"Command":
|
'Command':
|
||||||
command == null ? [] : List<dynamic>.from(command!.map((x) => x)),
|
command == null ? [] : List<dynamic>.from(command!.map((x) => x)),
|
||||||
"Created": created?.toIso8601String(),
|
'Created': created?.toIso8601String(),
|
||||||
"Exited": exited,
|
'Exited': exited,
|
||||||
"Id": id,
|
'Id': id,
|
||||||
"Image": image,
|
'Image': image,
|
||||||
"Names": names == null ? [] : List<dynamic>.from(names!.map((x) => x)),
|
'Names': names == null ? [] : List<dynamic>.from(names!.map((x) => x)),
|
||||||
"StartedAt": startedAt,
|
'StartedAt': startedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
final class DockerPs implements ContainerPs {
|
final class DockerPs implements ContainerPs {
|
||||||
final String? command;
|
|
||||||
final String? createdAt;
|
|
||||||
@override
|
@override
|
||||||
final String? id;
|
final String? id;
|
||||||
@override
|
@override
|
||||||
@@ -129,8 +128,6 @@ final class DockerPs implements ContainerPs {
|
|||||||
String? disk;
|
String? disk;
|
||||||
|
|
||||||
DockerPs({
|
DockerPs({
|
||||||
this.command,
|
|
||||||
this.createdAt,
|
|
||||||
this.id,
|
this.id,
|
||||||
this.image,
|
this.image,
|
||||||
this.names,
|
this.names,
|
||||||
@@ -141,10 +138,13 @@ final class DockerPs implements ContainerPs {
|
|||||||
String? get name => names;
|
String? get name => names;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get cmd => command;
|
String? get cmd => null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get running => state == 'running';
|
bool get running {
|
||||||
|
if (state?.contains('Exited') == true) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void parseStats(String s) {
|
void parseStats(String s) {
|
||||||
@@ -155,26 +155,15 @@ final class DockerPs implements ContainerPs {
|
|||||||
disk = stats['BlockIO'];
|
disk = stats['BlockIO'];
|
||||||
}
|
}
|
||||||
|
|
||||||
factory DockerPs.fromRawJson(String str) =>
|
/// CONTAINER ID NAMES IMAGE STATUS
|
||||||
DockerPs.fromJson(json.decode(str));
|
/// a049d689e7a1 aria2-pro p3terx/aria2-pro Up 3 weeks
|
||||||
|
factory DockerPs.parse(String raw) {
|
||||||
String toRawJson() => json.encode(toJson());
|
final parts = raw.split(Miscs.multiBlankreg);
|
||||||
|
return DockerPs(
|
||||||
factory DockerPs.fromJson(Map<String, dynamic> json) => DockerPs(
|
id: parts[0],
|
||||||
command: json["Command"],
|
state: parts[1],
|
||||||
createdAt: json["CreatedAt"],
|
names: parts[2],
|
||||||
id: json["ID"],
|
image: parts[3].trim(),
|
||||||
image: json["Image"],
|
);
|
||||||
names: json["Names"],
|
}
|
||||||
state: json["State"],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
"Command": command,
|
|
||||||
"CreatedAt": createdAt,
|
|
||||||
"ID": id,
|
|
||||||
"Image": image,
|
|
||||||
"Names": names,
|
|
||||||
"State": state,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:toolbox/data/model/container/image.dart';
|
import 'package:server_box/data/model/container/image.dart';
|
||||||
import 'package:toolbox/data/model/container/ps.dart';
|
import 'package:server_box/data/model/container/ps.dart';
|
||||||
|
|
||||||
enum ContainerType {
|
enum ContainerType {
|
||||||
docker,
|
docker,
|
||||||
@@ -7,7 +7,7 @@ enum ContainerType {
|
|||||||
;
|
;
|
||||||
|
|
||||||
ContainerPs Function(String str) get ps => switch (this) {
|
ContainerPs Function(String str) get ps => switch (this) {
|
||||||
ContainerType.docker => DockerPs.fromRawJson,
|
ContainerType.docker => DockerPs.parse,
|
||||||
ContainerType.podman => PodmanPs.fromRawJson,
|
ContainerType.podman => PodmanPs.fromRawJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:toolbox/data/model/server/dist.dart';
|
import 'package:server_box/data/model/server/dist.dart';
|
||||||
|
|
||||||
enum PkgManager {
|
enum PkgManager {
|
||||||
apt,
|
apt,
|
||||||
|
|||||||