mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-15 12:44:59 +01:00
Compare commits
74 Commits
v1.0.1231
...
lollipopki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0928e2f6 | ||
|
|
61f161d8a6 | ||
|
|
52c80795f4 | ||
|
|
09f1ab2cf2 | ||
|
|
2eeb55c1d8 | ||
|
|
6738ac94f8 | ||
|
|
827d40b8b5 | ||
|
|
928f2becf1 | ||
|
|
7d30af44d6 | ||
|
|
35349a90eb | ||
|
|
8be9b9b10b | ||
|
|
c51cf62015 | ||
|
|
8589b3b4d7 | ||
|
|
7693e30cbf | ||
|
|
874d28be12 | ||
|
|
06070c29b9 | ||
|
|
bb0ada12e6 | ||
|
|
9ceeaf7cc4 | ||
|
|
29a57ad742 | ||
|
|
2c495a44c3 | ||
|
|
cc300c141a | ||
|
|
26efb8e185 | ||
|
|
06ed38ff45 | ||
|
|
7c35abe30e | ||
|
|
78ef181d4a | ||
|
|
3f15caeaf2 | ||
|
|
6458e736fa | ||
|
|
99fda8b747 | ||
|
|
c5cbb12ac3 | ||
|
|
038f0d4d77 | ||
|
|
141519d952 | ||
|
|
75d1a59e77 | ||
|
|
ca4e65d7a5 | ||
|
|
ffda27d057 | ||
|
|
c548b4ef48 | ||
|
|
70040c5840 | ||
|
|
5272324be6 | ||
|
|
8cbb48ed67 | ||
|
|
03720fa322 | ||
|
|
0b51719070 | ||
|
|
a84231393d | ||
|
|
d6c2cafce7 | ||
|
|
729b76177e | ||
|
|
860c11d4a8 | ||
|
|
bd949288ed | ||
|
|
bb3e3b4848 | ||
|
|
3307fca620 | ||
|
|
da8517bcf7 | ||
|
|
f68c4a851b | ||
|
|
17db393c12 | ||
|
|
275581cfa3 | ||
|
|
d7168ea1ff | ||
|
|
fd2bf08f78 | ||
|
|
98e13c39cf | ||
|
|
e70abeef04 | ||
|
|
194774d6fb | ||
|
|
640d61bab9 | ||
|
|
7f4cf22cc9 | ||
|
|
05a927753f | ||
|
|
0c7b72fb2c | ||
|
|
a869b97502 | ||
|
|
eadd343205 | ||
|
|
1bac986fe0 | ||
|
|
a94be6c2c3 | ||
|
|
fc8e9b4bb1 | ||
|
|
ec4b633889 | ||
|
|
e51804fa70 | ||
|
|
2466341999 | ||
|
|
929061213f | ||
|
|
6b52679942 | ||
|
|
efc0315c93 | ||
|
|
8e4c2a7cde | ||
|
|
4ec7f5895e | ||
|
|
ee22cdb55f |
11
.github/workflows/analysis.yml
vendored
11
.github/workflows/analysis.yml
vendored
@@ -16,18 +16,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable' # or: 'beta', 'dev' or 'master'
|
channel: 'stable'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
# Uncomment this step to verify the use of 'dart format' on each commit.
|
|
||||||
- name: Verify formatting
|
|
||||||
run: dart format --output=none .
|
|
||||||
|
|
||||||
# Consider passing '--fatal-infos' for slightly stricter analysis.
|
# Consider passing '--fatal-infos' for slightly stricter analysis.
|
||||||
- name: Analyze project source
|
- name: Analyze project source
|
||||||
run: dart analyze
|
run: dart analyze
|
||||||
|
|||||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -9,18 +9,23 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
# Set by fl_build
|
||||||
|
# env:
|
||||||
|
# APP_NAME: ServerBox
|
||||||
|
# BUILD_NUMBER: ${{ github.ref_name }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
releaseAndroid:
|
releaseAndroid:
|
||||||
name: Release android
|
name: Release android
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Install Flutter
|
- name: Install Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: "3.35.1"
|
flutter-version: "3.38.0"
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
@@ -48,10 +53,10 @@ jobs:
|
|||||||
|
|
||||||
releaseLinux:
|
releaseLinux:
|
||||||
name: Release linux
|
name: Release linux
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Install Flutter
|
- name: Install Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -77,7 +82,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Install Flutter
|
- name: Install Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -95,19 +100,15 @@ jobs:
|
|||||||
# runs-on: macos-latest
|
# runs-on: macos-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Checkout
|
# - name: Checkout
|
||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v6
|
||||||
# - name: Install Flutter
|
# - name: Install Flutter
|
||||||
# uses: subosito/flutter-action@v2
|
# uses: subosito/flutter-action@v2
|
||||||
# with:
|
|
||||||
# channel: 'stable'
|
|
||||||
# flutter-version: '3.32.1'
|
|
||||||
# - name: Build
|
# - name: Build
|
||||||
# run: dart run fl_build -p ios,mac
|
# run: dart run fl_build -p ios
|
||||||
# - name: Create Release
|
# - name: Create Release
|
||||||
# uses: softprops/action-gh-release@v2
|
# uses: softprops/action-gh-release@v2
|
||||||
# with:
|
# with:
|
||||||
# files: |
|
# files: |
|
||||||
# ${{ env.APP_NAME }}_universal_macos.zip
|
|
||||||
# ${{ env.APP_NAME }}_universal.ipa
|
# ${{ env.APP_NAME }}_universal.ipa
|
||||||
# env:
|
# env:
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
95
CLAUDE.md
Normal file
95
CLAUDE.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
- `flutter run` - Run the app in development mode
|
||||||
|
- `dart run fl_build -p PLATFORM` - Build the app for specific platform (see fl_build package)
|
||||||
|
- `dart run build_runner build --delete-conflicting-outputs` - Generate code for models with annotations (json_serializable, freezed, hive, riverpod)
|
||||||
|
- Every time you change model files, run this command to regenerate code (Hive adapters, Riverpod providers, etc.)
|
||||||
|
- Generated files include: `*.g.dart`, `*.freezed.dart` files
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- `flutter test` - Run unit tests
|
||||||
|
- `flutter test test/battery_test.dart` - Run specific test file
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This is a Flutter application for managing Linux servers with the following key architectural components:
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
- `lib/core/` - Core utilities, extensions, and routing
|
||||||
|
- `lib/data/` - Data layer with models, providers, and storage
|
||||||
|
- `model/` - Data models organized by feature (server, container, ssh, etc.)
|
||||||
|
- `provider/` - Riverpod providers for state management
|
||||||
|
- `store/` - Local storage implementations using Hive
|
||||||
|
- `lib/view/` - UI layer with pages and widgets
|
||||||
|
- `lib/generated/` - Generated localization files
|
||||||
|
- `lib/hive/` - Hive adapters for local storage
|
||||||
|
|
||||||
|
### Key Technologies
|
||||||
|
|
||||||
|
- **State Management**: Riverpod with code generation (riverpod_annotation)
|
||||||
|
- **Local Storage**: Hive for persistent data with generated adapters
|
||||||
|
- **SSH/SFTP**: Custom dartssh2 fork for server connections
|
||||||
|
- **Terminal**: Custom xterm.dart fork for SSH terminal interface
|
||||||
|
- **Networking**: dio for HTTP requests
|
||||||
|
- **Charts**: fl_chart for server status visualization
|
||||||
|
- **Localization**: Flutter's built-in i18n with ARB files
|
||||||
|
- **Code Generation**: Uses build_runner with json_serializable, freezed, hive_generator, riverpod_generator
|
||||||
|
|
||||||
|
### Data Models
|
||||||
|
|
||||||
|
- Server management models in `lib/data/model/server/`
|
||||||
|
- Container/Docker models in `lib/data/model/container/`
|
||||||
|
- SSH and SFTP models in respective directories
|
||||||
|
- Most models use freezed for immutability and json_annotation for serialization
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Server status monitoring (CPU, memory, disk, network)
|
||||||
|
- SSH terminal with virtual keyboard
|
||||||
|
- SFTP file browser
|
||||||
|
- Docker container management
|
||||||
|
- Process and systemd service management
|
||||||
|
- Server snippets and custom commands
|
||||||
|
- Multi-language support (12+ languages)
|
||||||
|
- Cross-platform support (iOS, Android, macOS, Linux, Windows)
|
||||||
|
|
||||||
|
### State Management Pattern
|
||||||
|
|
||||||
|
- Uses Riverpod providers for dependency injection and state management
|
||||||
|
- Uses Freezed for immutable state models
|
||||||
|
- Providers are organized by feature in `lib/data/provider/`
|
||||||
|
- State is often persisted using Hive stores in `lib/data/store/`
|
||||||
|
|
||||||
|
### Build System
|
||||||
|
|
||||||
|
- Uses custom `fl_build` package for cross-platform building
|
||||||
|
- `make.dart` script handles pre/post build tasks (metadata generation)
|
||||||
|
- Supports building for multiple platforms with platform-specific configurations
|
||||||
|
- Many dependencies are custom forks hosted on GitHub (dartssh2, xterm, fl_lib, etc.)
|
||||||
|
|
||||||
|
### Important Notes
|
||||||
|
|
||||||
|
- **Never run code formatting commands** - The codebase has specific formatting that should not be changed
|
||||||
|
- **Always run code generation** after modifying models with annotations (freezed, json_serializable, hive, riverpod)
|
||||||
|
- Generated files (`*.g.dart`, `*.freezed.dart`) should not be manually edited
|
||||||
|
- AGAIN, NEVER run code formatting commands.
|
||||||
|
- USE dependency injection via GetIt for services like Stores, Services and etc.
|
||||||
|
- Generate all l10n files using `flutter gen-l10n` command after modifying ARB files.
|
||||||
|
- USE `hive_ce` not `hive` package for Hive integration.
|
||||||
|
- Which no need to config `HiveField` and `HiveType` manually.
|
||||||
|
- USE widgets and utilities from `fl_lib` package for common functionalities.
|
||||||
|
- Such as `CustomAppBar`, `context.showRoundDialog`, `Input`, `Btnx.cancelOk`, etc.
|
||||||
|
- You can use context7 MCP to search `lppcg fl_lib KEYWORD` to find relevant widgets and utilities.
|
||||||
|
- USE `libL10n` and `l10n` for localization strings.
|
||||||
|
- `libL10n` is from `fl_lib` package, and `l10n` is from this project.
|
||||||
|
- Before adding new strings, check if it already exists in `libL10n`.
|
||||||
|
- Prioritize using strings from `libL10n` to avoid duplication, even if the meaning is not 100% exact, just use the substitution of `libL10n`.
|
||||||
|
- Split UI into Widget build, Actions, Utils. use `extension on` to achieve this
|
||||||
143
LICENSE
143
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@@ -5,7 +5,7 @@ English | [简体中文](README_zh.md)
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a>
|
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a>
|
||||||
<img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan">
|
<img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan">
|
||||||
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-yellow">
|
<img alt="license" src="https://img.shields.io/badge/license-AGPLv3-yellow">
|
||||||
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,4 +85,4 @@ If I forgot to add your name to the contributors list, please add a comment in t
|
|||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
`GPL v3 lollipopkit`
|
`AGPL v3 lollipopkit & all contributors`
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a>
|
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a>
|
||||||
<img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan">
|
<img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan">
|
||||||
<img alt="license" src="https://img.shields.io/badge/证书-GPLv3-yellow">
|
<img alt="license" src="https://img.shields.io/badge/证书-AGPLv3-yellow">
|
||||||
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -86,4 +86,4 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
|
|||||||
|
|
||||||
## 📝 协议
|
## 📝 协议
|
||||||
|
|
||||||
`GPL v3 lollipopkit`
|
`AGPL v3 lollipopkit & 所有贡献者`
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ android.applicationVariants.all { variant ->
|
|||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
|
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
|
||||||
if (abiVersionCode != null) {
|
if (abiVersionCode != null) {
|
||||||
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
|
output.versionCodeOverride = variant.versionCode * 100 + abiVersionCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package tech.lolli.toolbox
|
|||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -16,8 +18,7 @@ class ForegroundService : Service() {
|
|||||||
var isRunning: Boolean = false
|
var isRunning: Boolean = false
|
||||||
}
|
}
|
||||||
private val chanId = "ForegroundServiceChannel"
|
private val chanId = "ForegroundServiceChannel"
|
||||||
private val GROUP_KEY = "ssh_sessions_group"
|
private val NOTIFICATION_ID = 1000
|
||||||
private val SUMMARY_ID = 1000
|
|
||||||
private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND"
|
private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND"
|
||||||
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
|
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
|
||||||
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
|
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
|
||||||
@@ -49,19 +50,22 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
try {
|
try {
|
||||||
|
// Check notification permission for Android 13+
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
androidx.core.content.ContextCompat.checkSelfPermission(
|
androidx.core.content.ContextCompat.checkSelfPermission(
|
||||||
this, android.Manifest.permission.POST_NOTIFICATIONS
|
this, android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
Log.w("ForegroundService", "Notification permission denied. Stopping service.")
|
Log.w("ForegroundService", "Notification permission denied. Stopping service gracefully.")
|
||||||
stopForegroundService()
|
// Don't call stopForegroundService() here as we haven't started foreground yet
|
||||||
|
stopSelf()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
Log.w("ForegroundService", "onStartCommand called with null intent")
|
Log.w("ForegroundService", "onStartCommand called with null intent")
|
||||||
stopForegroundService()
|
// Don't call stopForegroundService() here as we haven't started foreground yet
|
||||||
|
stopSelf()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,9 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
return when (action) {
|
return when (action) {
|
||||||
ACTION_STOP_FOREGROUND -> {
|
ACTION_STOP_FOREGROUND -> {
|
||||||
|
// Notify Flutter to stop all connections before stopping service
|
||||||
|
val stopAllIntent = Intent("tech.lolli.toolbox.STOP_ALL_CONNECTIONS")
|
||||||
|
sendBroadcast(stopAllIntent)
|
||||||
clearAll()
|
clearAll()
|
||||||
stopForegroundService()
|
stopForegroundService()
|
||||||
START_NOT_STICKY
|
START_NOT_STICKY
|
||||||
@@ -81,7 +88,7 @@ class ForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Default bring up foreground with placeholder
|
// Default bring up foreground with placeholder
|
||||||
ensureForeground(createSummaryNotification(0, emptyList()))
|
ensureForeground(createMergedNotification(0, emptyList(), emptyList()))
|
||||||
START_STICKY
|
START_STICKY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,37 +106,67 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val manager = getSystemService(NotificationManager::class.java)
|
try {
|
||||||
if (manager == null) {
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
Log.e("ForegroundService", "Failed to get NotificationManager")
|
if (manager == null) {
|
||||||
return
|
Log.e("ForegroundService", "Failed to get NotificationManager")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
chanId,
|
||||||
|
"ForegroundServiceChannel",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = "For foreground service"
|
||||||
|
}
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
Log.d("ForegroundService", "Notification channel created successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError("Failed to create notification channel", e)
|
||||||
}
|
}
|
||||||
val serviceChannel = NotificationChannel(
|
|
||||||
chanId,
|
|
||||||
"ForegroundServiceChannel",
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
|
||||||
).apply {
|
|
||||||
description = "For foreground service"
|
|
||||||
}
|
|
||||||
manager.createNotificationChannel(serviceChannel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureForeground(notification: Notification) {
|
private fun ensureForeground(notification: Notification) {
|
||||||
try {
|
try {
|
||||||
|
// Double-check notification permission before starting foreground service
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
|
androidx.core.content.ContextCompat.checkSelfPermission(
|
||||||
|
this, android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
Log.w("ForegroundService", "Cannot start foreground service without notification permission")
|
||||||
|
stopSelf()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!isFgStarted) {
|
if (!isFgStarted) {
|
||||||
startForeground(SUMMARY_ID, notification)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
isFgStarted = true
|
isFgStarted = true
|
||||||
|
Log.d("ForegroundService", "Foreground service started successfully")
|
||||||
} else {
|
} else {
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
nm?.notify(SUMMARY_ID, notification)
|
if (nm != null) {
|
||||||
|
nm.notify(NOTIFICATION_ID, notification)
|
||||||
|
} else {
|
||||||
|
Log.w("ForegroundService", "NotificationManager is null, cannot update notification")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
logError("Security exception when starting foreground service (likely missing permission)", e)
|
||||||
|
stopSelf()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError("Failed to start/update foreground", e)
|
logError("Failed to start/update foreground", e)
|
||||||
|
// Don't stop the service for other exceptions, just log them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSummaryNotification(count: Int, lines: List<String>): Notification {
|
|
||||||
|
private fun createMergedNotification(count: Int, lines: List<String>, sessions: List<SessionItem>): Notification {
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
|
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
|
||||||
@@ -140,24 +177,66 @@ class ForegroundService : Service() {
|
|||||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
Notification.Builder(this, chanId)
|
Notification.Builder(this, chanId)
|
||||||
} else {
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
Notification.Builder(this)
|
Notification.Builder(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val inbox = Notification.InboxStyle()
|
// Use the earliest session's start time for chronometer
|
||||||
lines.forEach { inbox.addLine(it) }
|
val earliestStartTime = sessions.minOfOrNull { it.startWhen } ?: System.currentTimeMillis()
|
||||||
|
|
||||||
return builder
|
val title = when (count) {
|
||||||
.setContentTitle("SSH sessions: $count active")
|
0 -> "Server Box"
|
||||||
.setContentText(if (lines.isNotEmpty()) lines.first() else "Running")
|
1 -> sessions.first().title
|
||||||
|
else -> "SSH sessions: $count active"
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentText = when (count) {
|
||||||
|
0 -> "Ready for connections"
|
||||||
|
1 -> {
|
||||||
|
val session = sessions.first()
|
||||||
|
"${session.subtitle} · ${session.status}"
|
||||||
|
}
|
||||||
|
else -> "Multiple SSH connections active"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For multiple sessions, show details in expanded view
|
||||||
|
val style = if (count > 1) {
|
||||||
|
val inbox = Notification.InboxStyle()
|
||||||
|
val maxLines = 5
|
||||||
|
val displayLines = if (lines.size > maxLines) {
|
||||||
|
lines.take(maxLines) + "...and ${lines.size - maxLines} more"
|
||||||
|
} else {
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
displayLines.forEach { inbox.addLine(it) }
|
||||||
|
inbox.setBigContentTitle(title)
|
||||||
|
inbox
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val notification = builder
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(contentText)
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
.setStyle(inbox)
|
.setWhen(earliestStartTime)
|
||||||
|
.setUsesChronometer(true)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setGroup(GROUP_KEY)
|
|
||||||
.setGroupSummary(true)
|
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.addAction(android.R.drawable.ic_delete, "Stop", stopPending)
|
.addAction(
|
||||||
.build()
|
Notification.Action.Builder(
|
||||||
|
Icon.createWithResource(this, android.R.drawable.ic_delete),
|
||||||
|
"Stop All",
|
||||||
|
stopPending
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (style != null) {
|
||||||
|
notification.setStyle(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUpdateSessions(payload: String) {
|
private fun handleUpdateSessions(payload: String) {
|
||||||
@@ -192,71 +271,21 @@ class ForegroundService : Service() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build per-session notifications
|
// Cancel any existing individual notifications (we only show merged notification now)
|
||||||
val currentIds = mutableSetOf<Int>()
|
val toCancel = postedIds.toSet()
|
||||||
val summaryLines = mutableListOf<String>()
|
|
||||||
sessions.forEach { s ->
|
|
||||||
// Assign a stable, collision-resistant id per session for this service lifecycle
|
|
||||||
val nid = notificationIdMap.getOrPut(s.id) { nextNotificationId.getAndIncrement() }
|
|
||||||
currentIds.add(nid)
|
|
||||||
summaryLines.add("${s.title}: ${s.status}")
|
|
||||||
|
|
||||||
val disconnectIntent = Intent(this, MainActivity::class.java).apply {
|
|
||||||
action = ACTION_DISCONNECT_SESSION
|
|
||||||
putExtra("session_id", s.id)
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
}
|
|
||||||
val disconnectPending = PendingIntent.getActivity(
|
|
||||||
this, nid, disconnectIntent, PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
Notification.Builder(this, chanId)
|
|
||||||
} else {
|
|
||||||
Notification.Builder(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val noti = builder
|
|
||||||
.setContentTitle(s.title)
|
|
||||||
.setContentText("${s.subtitle} · ${s.status}")
|
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
|
||||||
.setWhen(s.startWhen)
|
|
||||||
.setUsesChronometer(true)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setOnlyAlertOnce(true)
|
|
||||||
.setGroup(GROUP_KEY)
|
|
||||||
.addAction(android.R.drawable.ic_media_pause, "Disconnect", disconnectPending)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
nm.notify(nid, noti)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel stale ones
|
|
||||||
val toCancel = postedIds - currentIds
|
|
||||||
toCancel.forEach { nm.cancel(it) }
|
toCancel.forEach { nm.cancel(it) }
|
||||||
// Clean up id mappings for canceled notifications to prevent growth
|
|
||||||
if (toCancel.isNotEmpty()) {
|
|
||||||
val keysToRemove = notificationIdMap.filterValues { it in toCancel }.keys
|
|
||||||
keysToRemove.forEach { notificationIdMap.remove(it) }
|
|
||||||
}
|
|
||||||
postedIds.clear()
|
postedIds.clear()
|
||||||
postedIds.addAll(currentIds)
|
notificationIdMap.clear()
|
||||||
|
|
||||||
// Post/update summary and ensure foreground
|
// Create merged notification content
|
||||||
val maxSummaryLines = 5
|
val summaryLines = sessions.map { "${it.title}: ${it.status}" }
|
||||||
val truncated = summaryLines.size > maxSummaryLines
|
val mergedNotification = createMergedNotification(sessions.size, summaryLines, sessions)
|
||||||
val displaySummaryLines = if (truncated) {
|
ensureForeground(mergedNotification)
|
||||||
summaryLines.take(maxSummaryLines) + "...and ${summaryLines.size - maxSummaryLines} more"
|
|
||||||
} else {
|
|
||||||
summaryLines
|
|
||||||
}
|
|
||||||
val summary = createSummaryNotification(sessions.size, displaySummaryLines)
|
|
||||||
ensureForeground(summary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAll() {
|
private fun clearAll() {
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
nm?.cancel(SUMMARY_ID)
|
nm?.cancel(NOTIFICATION_ID)
|
||||||
postedIds.forEach { id -> nm?.cancel(id) }
|
postedIds.forEach { id -> nm?.cancel(id) }
|
||||||
postedIds.clear()
|
postedIds.clear()
|
||||||
isFgStarted = false
|
isFgStarted = false
|
||||||
@@ -272,7 +301,10 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
private fun stopForegroundService() {
|
private fun stopForegroundService() {
|
||||||
try {
|
try {
|
||||||
stopForeground(true)
|
if (isFgStarted) {
|
||||||
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
|
isFgStarted = false
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError("Error stopping foreground", e)
|
logError("Error stopping foreground", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.IntentFilter
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
@@ -16,6 +19,8 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
private lateinit var channel: MethodChannel
|
private lateinit var channel: MethodChannel
|
||||||
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
|
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
|
||||||
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
|
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
|
||||||
|
private val ACTION_STOP_ALL_CONNECTIONS = "tech.lolli.toolbox.STOP_ALL_CONNECTIONS"
|
||||||
|
private var stopAllReceiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
@@ -92,24 +97,32 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
|
|
||||||
// Handle intent if launched via notification action
|
// Handle intent if launched via notification action
|
||||||
handleActionIntent(intent)
|
handleActionIntent(intent)
|
||||||
|
|
||||||
|
// Register broadcast receiver for stop all connections
|
||||||
|
setupStopAllReceiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reqPerm() {
|
private fun reqPerm() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||||
|
|
||||||
// Check if we already have the permission to avoid unnecessary prompts
|
try {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
// Check if we already have the permission to avoid unnecessary prompts
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
try {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Check if we should show rationale
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
|
||||||
|
android.util.Log.i("MainActivity", "User previously denied notification permission")
|
||||||
|
}
|
||||||
|
|
||||||
ActivityCompat.requestPermissions(
|
ActivityCompat.requestPermissions(
|
||||||
this,
|
this,
|
||||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
123,
|
123,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
// Log error but don't crash
|
|
||||||
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log error but don't crash
|
||||||
|
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,4 +154,52 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupStopAllReceiver() {
|
||||||
|
stopAllReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (intent?.action == ACTION_STOP_ALL_CONNECTIONS && ::channel.isInitialized) {
|
||||||
|
try {
|
||||||
|
channel.invokeMethod("stopAllConnections", null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("MainActivity", "Failed to invoke stopAllConnections: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val filter = IntentFilter(ACTION_STOP_ALL_CONNECTIONS)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.registerReceiver(this, stopAllReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
registerReceiver(stopAllReceiver, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == 123) {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
android.util.Log.i("MainActivity", "Notification permission granted")
|
||||||
|
} else {
|
||||||
|
android.util.Log.w("MainActivity", "Notification permission denied")
|
||||||
|
// Optionally inform user about the limitation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
stopAllReceiver?.let {
|
||||||
|
try {
|
||||||
|
unregisterReceiver(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("MainActivity", "Failed to unregister receiver: ${e.message}")
|
||||||
|
}
|
||||||
|
stopAllReceiver = null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '8.6.0' apply false
|
id "com.android.application" version '8.9.1' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
|
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
fastlane/metadata/android/ru/full_description.txt
Normal file
7
fastlane/metadata/android/ru/full_description.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Проект на базе Flutter, предоставляющий диаграммы состояний серверов под Linux, Unix и Windows и инструменты для управления ими.
|
||||||
|
|
||||||
|
Особая благодарность dartssh2 и xterm.dart.
|
||||||
|
|
||||||
|
* Диаграмма состояния (ЦП, датчики, видеокарта…), SSH Term, SFTP, Docker, пакеты, процессы…
|
||||||
|
* Платформозависимые: биометрическая аутентификация, push-уведомления, виджет, приложение для watchOS…
|
||||||
|
* Многоязычная поддержка: English, 简体中文; Deutsch, 繁體中文, Indonesian, Français, Dutch; Español, Русский язык, Português, 日本語
|
||||||
1
fastlane/metadata/android/ru/short_description.txt
Normal file
1
fastlane/metadata/android/ru/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Приложение для мониторинга серверов и набор инструментов управления ими
|
||||||
@@ -88,19 +88,19 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741
|
||||||
file_picker: fb04e739ae6239a76ce1f571863a196a922c87d4
|
file_picker: fb04e739ae6239a76ce1f571863a196a922c87d4
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2
|
icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2
|
||||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
plain_notification_token: 047876b9d80a5b93565ddcc13a487a7e7b906f7d
|
plain_notification_token: 047876b9d80a5b93565ddcc13a487a7e7b906f7d
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
watch_connectivity: 88e5bea25b473e66ef8d3f960954d154ed0356d6
|
watch_connectivity: 88e5bea25b473e66ef8d3f960954d154ed0356d6
|
||||||
|
|
||||||
|
|||||||
@@ -748,7 +748,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -758,7 +758,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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";
|
||||||
@@ -884,7 +884,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -894,7 +894,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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";
|
||||||
@@ -912,7 +912,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -922,7 +922,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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";
|
||||||
@@ -943,7 +943,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -956,7 +956,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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;
|
||||||
@@ -982,7 +982,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -995,7 +995,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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)";
|
||||||
@@ -1018,7 +1018,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1031,7 +1031,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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)";
|
||||||
@@ -1054,7 +1054,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1066,7 +1066,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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;
|
||||||
@@ -1095,7 +1095,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1107,7 +1107,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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;
|
||||||
@@ -1133,7 +1133,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 = 1231;
|
CURRENT_PROJECT_VERSION = 1291;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1145,7 +1145,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.1231;
|
MARKETING_VERSION = 1.0.1291;
|
||||||
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;
|
||||||
|
|||||||
38
lib/app.dart
38
lib/app.dart
@@ -3,6 +3,7 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:fl_lib/generated/l10n/lib_l10n.dart';
|
import 'package:fl_lib/generated/l10n/lib_l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
|
import 'package:server_box/core/app_navigator.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/data/res/build_data.dart';
|
import 'package:server_box/data/res/build_data.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
@@ -11,12 +12,20 @@ import 'package:server_box/view/page/home.dart';
|
|||||||
|
|
||||||
part 'intro.dart';
|
part 'intro.dart';
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyApp> createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> {
|
||||||
|
late final Future<List<IntroPageBuilder>> _introFuture = _IntroPage.builders;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_setup(context);
|
_setup(context);
|
||||||
|
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: RNodes.app,
|
listenable: RNodes.app,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
@@ -31,6 +40,7 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _build(BuildContext context) {
|
Widget _build(BuildContext context) {
|
||||||
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
||||||
|
|
||||||
UIs.colorSeed = colorSeed;
|
UIs.colorSeed = colorSeed;
|
||||||
UIs.primaryColor = colorSeed;
|
UIs.primaryColor = colorSeed;
|
||||||
|
|
||||||
@@ -53,12 +63,31 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget _buildDynamicColor(BuildContext context) {
|
Widget _buildDynamicColor(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: (light, dark) {
|
builder: (light, dark) {
|
||||||
final lightTheme = ThemeData(useMaterial3: true, colorScheme: light);
|
final lightSeed = light?.primary;
|
||||||
final darkTheme = ThemeData(useMaterial3: true, brightness: Brightness.dark, colorScheme: dark);
|
final darkSeed = dark?.primary;
|
||||||
|
|
||||||
|
final lightTheme = ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorSchemeSeed: lightSeed,
|
||||||
|
appBarTheme: AppBarTheme(scrolledUnderElevation: 0.0),
|
||||||
|
);
|
||||||
|
final darkTheme = ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
colorSchemeSeed: darkSeed,
|
||||||
|
appBarTheme: AppBarTheme(scrolledUnderElevation: 0.0),
|
||||||
|
);
|
||||||
|
|
||||||
if (context.isDark && dark != null) {
|
if (context.isDark && dark != null) {
|
||||||
UIs.primaryColor = dark.primary;
|
UIs.primaryColor = dark.primary;
|
||||||
|
UIs.colorSeed = dark.primary;
|
||||||
} else if (!context.isDark && light != null) {
|
} else if (!context.isDark && light != null) {
|
||||||
UIs.primaryColor = light.primary;
|
UIs.primaryColor = light.primary;
|
||||||
|
UIs.colorSeed = light.primary;
|
||||||
|
} else {
|
||||||
|
final fallbackColor = Color(Stores.setting.colorSeed.fetch());
|
||||||
|
UIs.primaryColor = fallbackColor;
|
||||||
|
UIs.colorSeed = fallbackColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
return _buildApp(context, light: lightTheme, dark: darkTheme);
|
||||||
@@ -78,6 +107,7 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
key: ValueKey(locale),
|
key: ValueKey(locale),
|
||||||
|
navigatorKey: AppNavigator.key,
|
||||||
builder: ResponsivePoints.builder,
|
builder: ResponsivePoints.builder,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
localizationsDelegates: const [LibLocalizations.delegate, ...AppLocalizations.localizationsDelegates],
|
localizationsDelegates: const [LibLocalizations.delegate, ...AppLocalizations.localizationsDelegates],
|
||||||
@@ -89,7 +119,7 @@ class MyApp extends StatelessWidget {
|
|||||||
theme: light.fixWindowsFont,
|
theme: light.fixWindowsFont,
|
||||||
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
|
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
|
||||||
home: FutureBuilder<List<IntroPageBuilder>>(
|
home: FutureBuilder<List<IntroPageBuilder>>(
|
||||||
future: _IntroPage.builders,
|
future: _introFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
context.setLibL10n();
|
context.setLibL10n();
|
||||||
final appL10n = AppLocalizations.of(context);
|
final appL10n = AppLocalizations.of(context);
|
||||||
|
|||||||
8
lib/core/app_navigator.dart
Normal file
8
lib/core/app_navigator.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
/// Global navigator access used for cross-cutting flows (e.g. dialogs).
|
||||||
|
abstract final class AppNavigator {
|
||||||
|
static final key = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
static BuildContext? get context => key.currentContext;
|
||||||
|
}
|
||||||
@@ -35,8 +35,8 @@ abstract final class MethodChans {
|
|||||||
try {
|
try {
|
||||||
Loggers.app.info('Updating Android sessions: $payload');
|
Loggers.app.info('Updating Android sessions: $payload');
|
||||||
await _channel.invokeMethod('updateSessions', payload);
|
await _channel.invokeMethod('updateSessions', payload);
|
||||||
} catch (_) {
|
} catch (e, s) {
|
||||||
// ignore
|
Loggers.app.warning('Failed to update Android sessions', e, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,8 @@ abstract final class MethodChans {
|
|||||||
try {
|
try {
|
||||||
final res = await _channel.invokeMethod('isServiceRunning');
|
final res = await _channel.invokeMethod('isServiceRunning');
|
||||||
return res == true;
|
return res == true;
|
||||||
} catch (_) {
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to check if Android service is running', e, s);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,9 @@ abstract final class MethodChans {
|
|||||||
try {
|
try {
|
||||||
Loggers.app.info('Starting iOS Live Activity: $payload');
|
Loggers.app.info('Starting iOS Live Activity: $payload');
|
||||||
await _channel.invokeMethod('startLiveActivity', payload);
|
await _channel.invokeMethod('startLiveActivity', payload);
|
||||||
} catch (_) {}
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to start iOS Live Activity', e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> updateLiveActivity(String payload) async {
|
static Future<void> updateLiveActivity(String payload) async {
|
||||||
@@ -65,7 +68,9 @@ abstract final class MethodChans {
|
|||||||
try {
|
try {
|
||||||
Loggers.app.info('Updating iOS Live Activity: $payload');
|
Loggers.app.info('Updating iOS Live Activity: $payload');
|
||||||
await _channel.invokeMethod('updateLiveActivity', payload);
|
await _channel.invokeMethod('updateLiveActivity', payload);
|
||||||
} catch (_) {}
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to update iOS Live Activity', e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> stopLiveActivity() async {
|
static Future<void> stopLiveActivity() async {
|
||||||
@@ -73,12 +78,16 @@ abstract final class MethodChans {
|
|||||||
try {
|
try {
|
||||||
Loggers.app.info('Stopping iOS Live Activity');
|
Loggers.app.info('Stopping iOS Live Activity');
|
||||||
await _channel.invokeMethod('stopLiveActivity');
|
await _channel.invokeMethod('stopLiveActivity');
|
||||||
} catch (_) {}
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to stop iOS Live Activity', e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a handler for native -> Flutter callbacks.
|
/// Register a handler for native -> Flutter callbacks.
|
||||||
/// Currently handles: `disconnectSession` with argument map {id: string}
|
/// Currently handles:
|
||||||
static void registerHandler(Future<void> Function(String id) onDisconnect) {
|
/// - `disconnectSession` with argument map {id: string}
|
||||||
|
/// - `stopAllConnections` with no arguments
|
||||||
|
static void registerHandler(Future<void> Function(String id) onDisconnect, [VoidCallback? onStopAll]) {
|
||||||
_channel.setMethodCallHandler((call) async {
|
_channel.setMethodCallHandler((call) async {
|
||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'disconnectSession':
|
case 'disconnectSession':
|
||||||
@@ -88,6 +97,9 @@ abstract final class MethodChans {
|
|||||||
await onDisconnect(id);
|
await onDisconnect(id);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case 'stopAllConnections':
|
||||||
|
onStopAll?.call();
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:server_box/data/helper/ssh_decoder.dart';
|
||||||
import 'package:server_box/data/model/server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
import 'package:server_box/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
@@ -170,4 +171,98 @@ extension SSHClientX on SSHClient {
|
|||||||
);
|
);
|
||||||
return ret.$2;
|
return ret.$2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs a command and decodes output safely with encoding fallback
|
||||||
|
///
|
||||||
|
/// [systemType] - The system type (affects encoding choice)
|
||||||
|
/// Runs a command and safely decodes the result
|
||||||
|
Future<String> runSafe(
|
||||||
|
String command, {
|
||||||
|
SystemType? systemType,
|
||||||
|
String? context,
|
||||||
|
}) async {
|
||||||
|
// Let SSH errors propagate with their original type (e.g., SSHError subclasses)
|
||||||
|
final result = await run(command);
|
||||||
|
|
||||||
|
// Only catch decoding failures and add context
|
||||||
|
try {
|
||||||
|
return SSHDecoder.decode(
|
||||||
|
result,
|
||||||
|
isWindows: systemType == SystemType.windows,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to decode command output${context != null ? " [$context]" : ""}: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a command with stdin and safely decodes stdout/stderr
|
||||||
|
Future<(String stdout, String stderr)> execSafe(
|
||||||
|
void Function(SSHSession session) callback, {
|
||||||
|
required String entry,
|
||||||
|
SystemType? systemType,
|
||||||
|
String? context,
|
||||||
|
}) async {
|
||||||
|
final stdoutBuilder = BytesBuilder(copy: false);
|
||||||
|
final stderrBuilder = BytesBuilder(copy: false);
|
||||||
|
final stdoutDone = Completer<void>();
|
||||||
|
final stderrDone = Completer<void>();
|
||||||
|
|
||||||
|
final session = await execute(entry);
|
||||||
|
|
||||||
|
session.stdout.listen(
|
||||||
|
(e) {
|
||||||
|
stdoutBuilder.add(e);
|
||||||
|
},
|
||||||
|
onDone: stdoutDone.complete,
|
||||||
|
onError: stdoutDone.completeError,
|
||||||
|
);
|
||||||
|
|
||||||
|
session.stderr.listen(
|
||||||
|
(e) {
|
||||||
|
stderrBuilder.add(e);
|
||||||
|
},
|
||||||
|
onDone: stderrDone.complete,
|
||||||
|
onError: stderrDone.completeError,
|
||||||
|
);
|
||||||
|
|
||||||
|
callback(session);
|
||||||
|
|
||||||
|
await stdoutDone.future;
|
||||||
|
await stderrDone.future;
|
||||||
|
|
||||||
|
final stdoutBytes = stdoutBuilder.takeBytes();
|
||||||
|
final stderrBytes = stderrBuilder.takeBytes();
|
||||||
|
|
||||||
|
// Only catch decoding failures, let other errors propagate
|
||||||
|
String stdout;
|
||||||
|
try {
|
||||||
|
stdout = SSHDecoder.decode(
|
||||||
|
stdoutBytes,
|
||||||
|
isWindows: systemType == SystemType.windows,
|
||||||
|
context: context != null ? '$context (stdout)' : 'stdout',
|
||||||
|
);
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to decode stdout${context != null ? " [$context]" : ""}: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String stderr;
|
||||||
|
try {
|
||||||
|
stderr = SSHDecoder.decode(
|
||||||
|
stderrBytes,
|
||||||
|
isWindows: systemType == SystemType.windows,
|
||||||
|
context: context != null ? '$context (stderr)' : 'stderr',
|
||||||
|
);
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to decode stderr${context != null ? " [$context]" : ""}: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (stdout, stderr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
411
lib/core/service/ssh_discovery.dart
Normal file
411
lib/core/service/ssh_discovery.dart
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:server_box/data/model/server/discovery_result.dart';
|
||||||
|
|
||||||
|
class SshDiscoveryService {
|
||||||
|
static const _sshPort = 22;
|
||||||
|
|
||||||
|
static Future<SshDiscoveryReport> discover([SshDiscoveryConfig config = const SshDiscoveryConfig()]) async {
|
||||||
|
final t0 = DateTime.now();
|
||||||
|
final candidates = <InternetAddress>{};
|
||||||
|
|
||||||
|
// 1) Get neighbors from ARP/NDP tables
|
||||||
|
candidates.addAll(await _neighborsIPv4());
|
||||||
|
candidates.addAll(await _neighborsIPv6());
|
||||||
|
|
||||||
|
// 2) Enumerate small subnets from local interfaces (IPv4 only)
|
||||||
|
final cidrs = await _localIPv4Cidrs();
|
||||||
|
for (final c in cidrs) {
|
||||||
|
if (c.prefix >= 24 && c.prefix <= 30) {
|
||||||
|
candidates.addAll(c.enumerateHosts(limit: config.hostEnumerationLimit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Optional: mDNS/Bonjour SSH services
|
||||||
|
if (config.enableMdns) {
|
||||||
|
candidates.addAll(await _mdnsSshCandidates());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out unwanted addresses: loopback, link-local, 0.0.0.0, broadcast, multicast
|
||||||
|
candidates.removeWhere(
|
||||||
|
(a) => a.isLoopback || a.isLinkLocal || a.address == '0.0.0.0' || _isBroadcastOrMulticast(a),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4) Concurrent SSH port scanning
|
||||||
|
final scanner = _Scanner(
|
||||||
|
timeout: Duration(milliseconds: config.timeoutMs),
|
||||||
|
maxConcurrency: config.maxConcurrency,
|
||||||
|
);
|
||||||
|
|
||||||
|
final results = await scanner.scan(candidates.toList(growable: false));
|
||||||
|
results.sort((a, b) => a.addr.address.compareTo(b.addr.address));
|
||||||
|
|
||||||
|
final discoveryResults = results
|
||||||
|
.map((r) => SshDiscoveryResult(ip: r.addr.address, port: _sshPort, banner: r.banner?.trim()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return SshDiscoveryReport(
|
||||||
|
generatedAt: DateTime.now().toIso8601String(),
|
||||||
|
durationMs: DateTime.now().difference(t0).inMilliseconds,
|
||||||
|
count: discoveryResults.length,
|
||||||
|
items: discoveryResults,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String?> _run(String exe, List<String> args, {Duration? timeout}) async {
|
||||||
|
try {
|
||||||
|
final p = await Process.start(exe, args, runInShell: false);
|
||||||
|
final out = await p.stdout
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.join()
|
||||||
|
.timeout(
|
||||||
|
timeout ?? const Duration(seconds: 5),
|
||||||
|
onTimeout: () {
|
||||||
|
p.kill();
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final code = await p.exitCode;
|
||||||
|
if (code == 0) return out;
|
||||||
|
// Some tools return non-zero but still have useful output
|
||||||
|
if (out.trim().isNotEmpty) return out;
|
||||||
|
return null;
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to run command: $exe ${args.join(' ')}', e, s);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get _isLinux => Platform.isLinux;
|
||||||
|
static bool get _isMac => Platform.isMacOS;
|
||||||
|
|
||||||
|
static Future<Set<InternetAddress>> _neighborsIPv4() async {
|
||||||
|
final set = <InternetAddress>{};
|
||||||
|
if (_isLinux) {
|
||||||
|
final s = await _run('ip', ['neigh']);
|
||||||
|
if (s != null) {
|
||||||
|
for (final line in const LineSplitter().convert(s)) {
|
||||||
|
final tok = line.split(RegExp(r'\s+'));
|
||||||
|
if (tok.isNotEmpty) {
|
||||||
|
final ip = tok[0];
|
||||||
|
if (InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv4) {
|
||||||
|
set.add(InternetAddress(ip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_isMac) {
|
||||||
|
final s = await _run('/usr/sbin/arp', ['-an']);
|
||||||
|
if (s != null) {
|
||||||
|
int matchCount = 0;
|
||||||
|
for (final line in const LineSplitter().convert(s)) {
|
||||||
|
final m = RegExp(r'\((\d+\.\d+\.\d+\.\d+)\)').firstMatch(line);
|
||||||
|
if (m != null) {
|
||||||
|
set.add(InternetAddress(m.group(1)!));
|
||||||
|
matchCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchCount == 0) {
|
||||||
|
Loggers.app.warning(
|
||||||
|
'[ssh_discovery] Warning: No ARP entries parsed on macOS. Output may be unexpected or localized. Output sample: ${s.length > 100 ? '${s.substring(0, 100)}...' : s}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Set<InternetAddress>> _neighborsIPv6() async {
|
||||||
|
final set = <InternetAddress>{};
|
||||||
|
if (_isLinux) {
|
||||||
|
final s = await _run('ip', ['-6', 'neigh']);
|
||||||
|
if (s != null) {
|
||||||
|
for (final line in const LineSplitter().convert(s)) {
|
||||||
|
final ip = line.split(RegExp(r'\s+')).firstOrNull;
|
||||||
|
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
|
||||||
|
set.add(InternetAddress(ip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_isMac) {
|
||||||
|
final s = await _run('/usr/sbin/ndp', ['-a']);
|
||||||
|
if (s != null) {
|
||||||
|
for (final line in const LineSplitter().convert(s)) {
|
||||||
|
final ip = line.trim().split(RegExp(r'\s+')).firstOrNull;
|
||||||
|
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
|
||||||
|
set.add(InternetAddress(ip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<_Cidr>> _localIPv4Cidrs() async {
|
||||||
|
final res = <_Cidr>[];
|
||||||
|
if (_isLinux) {
|
||||||
|
final s = await _run('ip', ['-o', '-4', 'addr', 'show', 'scope', 'global']);
|
||||||
|
if (s != null) {
|
||||||
|
for (final line in const LineSplitter().convert(s)) {
|
||||||
|
final m = RegExp(r'inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)').firstMatch(line);
|
||||||
|
if (m != null) {
|
||||||
|
final ip = InternetAddress(m.group(1)!);
|
||||||
|
final prefix = int.parse(m.group(2)!);
|
||||||
|
final mask = _prefixToMask(prefix);
|
||||||
|
final net = _networkAddress(ip, mask);
|
||||||
|
final brd = _broadcastAddress(ip, mask);
|
||||||
|
res.add(_Cidr(ip, prefix, mask, net, brd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_isMac) {
|
||||||
|
final s = await _run('/sbin/ifconfig', []);
|
||||||
|
if (s != null) {
|
||||||
|
for (final raw in const LineSplitter().convert(s)) {
|
||||||
|
final line = raw.trimRight();
|
||||||
|
final ifMatch = RegExp(r'^([a-z0-9]+):').firstMatch(line);
|
||||||
|
if (ifMatch != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.contains('inet ') && !line.contains('127.0.0.1')) {
|
||||||
|
try {
|
||||||
|
final ipm = RegExp(
|
||||||
|
r'inet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+0x([0-9a-fA-F]+)(?:\s+broadcast\s+(\d+\.\d+\.\d+\.\d+))?',
|
||||||
|
).firstMatch(line);
|
||||||
|
if (ipm == null) {
|
||||||
|
Loggers.app.warning('[ssh_discovery] Warning: Unexpected ifconfig line format: $line');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ip = InternetAddress(ipm.group(1)!);
|
||||||
|
final hexMask = int.parse(ipm.group(2)!, radix: 16);
|
||||||
|
final dotted =
|
||||||
|
'${(hexMask >> 24) & 0xff}.${(hexMask >> 16) & 0xff}.${(hexMask >> 8) & 0xff}.${hexMask & 0xff}';
|
||||||
|
final mask = InternetAddress(dotted);
|
||||||
|
final prefix = _maskToPrefix(mask.address);
|
||||||
|
final net = _networkAddress(ip, mask);
|
||||||
|
final brd = InternetAddress(ipm.group(3) ?? _broadcastAddress(ip, mask).address);
|
||||||
|
res.add(_Cidr(ip, prefix, mask, net, brd));
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('[ssh_discovery] Error parsing ifconfig output: $e, line: $line');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isBroadcastOrMulticast(InternetAddress a) {
|
||||||
|
// IPv4 broadcast: ends with .255 or is 255.255.255.255
|
||||||
|
if (a.type == InternetAddressType.IPv4) {
|
||||||
|
if (a.address == '255.255.255.255') return true;
|
||||||
|
if (a.address.split('.').last == '255') return true;
|
||||||
|
// Multicast: 224.0.0.0 - 239.255.255.255
|
||||||
|
final firstOctet = int.tryParse(a.address.split('.').first) ?? 0;
|
||||||
|
if (firstOctet >= 224 && firstOctet <= 239) return true;
|
||||||
|
} else if (a.type == InternetAddressType.IPv6) {
|
||||||
|
// IPv6 multicast: starts with ff
|
||||||
|
if (a.address.toLowerCase().startsWith('ff')) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Set<InternetAddress>> _mdnsSshCandidates() async {
|
||||||
|
final set = <InternetAddress>{};
|
||||||
|
if (_isMac) {
|
||||||
|
try {
|
||||||
|
final proc = await Process.start('/usr/bin/dns-sd', ['-B', '_ssh._tcp']);
|
||||||
|
final lines = <String>[];
|
||||||
|
final subscription = proc.stdout
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.transform(const LineSplitter())
|
||||||
|
.listen(lines.add);
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
proc.kill();
|
||||||
|
await subscription.cancel();
|
||||||
|
|
||||||
|
for (final l in lines) {
|
||||||
|
final m = RegExp(r'Add\s+\d+\s+(\S+)\.\s+_ssh\._tcp\.').firstMatch(l);
|
||||||
|
if (m != null) {
|
||||||
|
final name = m.group(1)!;
|
||||||
|
final det = await _run('/usr/bin/dns-sd', [
|
||||||
|
'-L',
|
||||||
|
name,
|
||||||
|
'_ssh._tcp',
|
||||||
|
'local.',
|
||||||
|
], timeout: const Duration(seconds: 3));
|
||||||
|
if (det != null) {
|
||||||
|
for (final ip in RegExp(
|
||||||
|
r'Address\s*=\s*([0-9a-fA-F:\.]+)',
|
||||||
|
).allMatches(det).map((e) => e.group(1)!)) {
|
||||||
|
final parsed = InternetAddress.tryParse(ip);
|
||||||
|
if (parsed != null) set.add(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to discover mDNS SSH candidates on macOS', e, s);
|
||||||
|
}
|
||||||
|
} else if (_isLinux) {
|
||||||
|
final s = await _run('/usr/bin/avahi-browse', ['-rat', '_ssh._tcp']);
|
||||||
|
if (s != null) {
|
||||||
|
for (final ip in RegExp(
|
||||||
|
r'address = \[(.*?)\]',
|
||||||
|
).allMatches(s).map((m) => m.group(1)!).where((e) => e.isNotEmpty)) {
|
||||||
|
final parsed = InternetAddress.tryParse(ip);
|
||||||
|
if (parsed != null) set.add(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Cidr {
|
||||||
|
final InternetAddress ip;
|
||||||
|
final int prefix;
|
||||||
|
final InternetAddress netmask;
|
||||||
|
final InternetAddress network;
|
||||||
|
final InternetAddress broadcast;
|
||||||
|
|
||||||
|
_Cidr(this.ip, this.prefix, this.netmask, this.network, this.broadcast);
|
||||||
|
|
||||||
|
Iterable<InternetAddress> enumerateHosts({int? limit}) sync* {
|
||||||
|
final n = _ipv4ToInt(network.address);
|
||||||
|
final b = _ipv4ToInt(broadcast.address);
|
||||||
|
int emitted = 0;
|
||||||
|
for (int v = n + 1; v <= b - 1; v++) {
|
||||||
|
if (limit != null && emitted >= limit) break;
|
||||||
|
emitted++;
|
||||||
|
yield InternetAddress(_intToIPv4(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '${network.address}/$prefix';
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScanResult {
|
||||||
|
final InternetAddress addr;
|
||||||
|
final String? banner;
|
||||||
|
_ScanResult(this.addr, this.banner);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Scanner {
|
||||||
|
final Duration timeout;
|
||||||
|
final int maxConcurrency;
|
||||||
|
_Scanner({required this.timeout, required this.maxConcurrency});
|
||||||
|
|
||||||
|
Future<List<_ScanResult>> scan(List<InternetAddress> addrs) async {
|
||||||
|
final sem = _Semaphore(maxConcurrency);
|
||||||
|
final futures = <Future<_ScanResult?>>[];
|
||||||
|
for (final a in addrs) {
|
||||||
|
futures.add(_guarded(sem, () => _probeSsh(a)));
|
||||||
|
}
|
||||||
|
final out = await Future.wait(futures);
|
||||||
|
return out.whereType<_ScanResult>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<_ScanResult?> _probeSsh(InternetAddress ip) async {
|
||||||
|
Socket? socket;
|
||||||
|
StreamSubscription? sub;
|
||||||
|
try {
|
||||||
|
socket = await Socket.connect(ip, SshDiscoveryService._sshPort, timeout: timeout);
|
||||||
|
socket.timeout(timeout);
|
||||||
|
final c = Completer<String?>();
|
||||||
|
sub = socket.listen(
|
||||||
|
(data) {
|
||||||
|
final s = utf8.decode(data, allowMalformed: true);
|
||||||
|
final line = s.split('\n').firstWhere((_) => true, orElse: () => s);
|
||||||
|
if (!c.isCompleted) {
|
||||||
|
c.complete(line.trim());
|
||||||
|
sub?.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
if (!c.isCompleted) c.complete(null);
|
||||||
|
},
|
||||||
|
onError: (_) {
|
||||||
|
if (!c.isCompleted) c.complete(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final banner = await c.future.timeout(timeout, onTimeout: () => null);
|
||||||
|
return _ScanResult(ip, banner);
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to probe SSH at ${ip.address}', e, s);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
sub?.cancel();
|
||||||
|
socket?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Semaphore {
|
||||||
|
int _permits;
|
||||||
|
final Queue<Completer<void>> _q = Queue();
|
||||||
|
_Semaphore(this._permits);
|
||||||
|
|
||||||
|
Future<T> withPermit<T>(Future<T> Function() fn) async {
|
||||||
|
if (_permits > 0) {
|
||||||
|
_permits--;
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} finally {
|
||||||
|
_permits++;
|
||||||
|
if (_q.isNotEmpty) _q.removeFirst().complete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final c = Completer<void>();
|
||||||
|
_q.add(c);
|
||||||
|
await c.future;
|
||||||
|
return withPermit(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> _guarded<T>(_Semaphore sem, Future<T> Function() fn) => sem.withPermit(fn);
|
||||||
|
|
||||||
|
// IPv4 utilities
|
||||||
|
|
||||||
|
int _ipv4ToInt(String ip) {
|
||||||
|
final p = ip.split('.').map(int.parse).toList();
|
||||||
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
String _intToIPv4(int v) => '${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${(v >> 8) & 0xff}.${v & 0xff}';
|
||||||
|
|
||||||
|
InternetAddress _prefixToMask(int prefix) {
|
||||||
|
final mask = prefix == 0 ? 0 : 0xffffffff << (32 - prefix);
|
||||||
|
return InternetAddress(_intToIPv4(mask & 0xffffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
int _maskToPrefix(String mask) {
|
||||||
|
final v = _ipv4ToInt(mask);
|
||||||
|
int c = 0;
|
||||||
|
for (int i = 31; i >= 0; i--) {
|
||||||
|
if ((v & (1 << i)) != 0) {
|
||||||
|
c++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetAddress _networkAddress(InternetAddress ip, InternetAddress mask) {
|
||||||
|
final v = _ipv4ToInt(ip.address) & _ipv4ToInt(mask.address);
|
||||||
|
return InternetAddress(_intToIPv4(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetAddress _broadcastAddress(InternetAddress ip, InternetAddress mask) {
|
||||||
|
final n = _ipv4ToInt(ip.address) & _ipv4ToInt(mask.address);
|
||||||
|
final b = n | (~_ipv4ToInt(mask.address) & 0xffffffff);
|
||||||
|
return InternetAddress(_intToIPv4(b));
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ final class BakSyncer extends SyncIface {
|
|||||||
return MergeableUtils.fromJsonString(content, pwd).$1;
|
return MergeableUtils.fromJsonString(content, pwd).$1;
|
||||||
}
|
}
|
||||||
return MergeableUtils.fromJsonString(content).$1;
|
return MergeableUtils.fromJsonString(content).$1;
|
||||||
} catch (_) {
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to parse backup file with password, trying without password', e, s);
|
||||||
// Fallback: try without password if detection failed
|
// Fallback: try without password if detection failed
|
||||||
return MergeableUtils.fromJsonString(content).$1;
|
return MergeableUtils.fromJsonString(content).$1;
|
||||||
}
|
}
|
||||||
|
|||||||
26
lib/core/utils/host_key_helper.dart
Normal file
26
lib/core/utils/host_key_helper.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:server_box/core/utils/server.dart';
|
||||||
|
import 'package:server_box/core/utils/ssh_auth.dart';
|
||||||
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
|
||||||
|
Future<bool> ensureHostKeyAcceptedForSftp(BuildContext context, Spi spi) async {
|
||||||
|
final known = Stores.setting.sshKnownHostFingerprints.get();
|
||||||
|
final hostId = spi.id.isNotEmpty ? spi.id : spi.oldId;
|
||||||
|
final prefix = '$hostId::';
|
||||||
|
if (known.keys.any((key) => key.startsWith(prefix))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final (result, error) = await context.showLoadingDialog<bool>(
|
||||||
|
fn: () async {
|
||||||
|
await ensureKnownHostKey(
|
||||||
|
spi,
|
||||||
|
onKeyboardInteractive: (_) => KeybordInteractive.defaultHandle(spi, ctx: context),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return error == null && result == true;
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:server_box/core/app_navigator.dart';
|
||||||
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:server_box/data/res/store.dart';
|
import 'package:server_box/data/res/store.dart';
|
||||||
@@ -29,11 +33,82 @@ enum GenSSHClientStatus { socket, key, pwd }
|
|||||||
String getPrivateKey(String id) {
|
String getPrivateKey(String id) {
|
||||||
final pki = Stores.key.fetchOne(id);
|
final pki = Stores.key.fetchOne(id);
|
||||||
if (pki == null) {
|
if (pki == null) {
|
||||||
throw SSHErr(type: SSHErrType.noPrivateKey, message: 'key [$id] not found');
|
throw SSHErr(type: SSHErrType.noPrivateKey, message: l10n.privateKeyNotFoundFmt(id));
|
||||||
}
|
}
|
||||||
return pki.key;
|
return pki.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Spi> resolveMergedJumpChain(
|
||||||
|
Spi target, {
|
||||||
|
List<Spi>? jumpChain,
|
||||||
|
}) {
|
||||||
|
final injectedSpiMap = <String, Spi>{};
|
||||||
|
if (jumpChain != null) {
|
||||||
|
for (final s in jumpChain) {
|
||||||
|
injectedSpiMap[s.id] = s;
|
||||||
|
if (s.oldId.isNotEmpty) {
|
||||||
|
injectedSpiMap[s.oldId] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spi resolveSpi(String id) {
|
||||||
|
final injected = injectedSpiMap[id];
|
||||||
|
if (injected != null) return injected;
|
||||||
|
if (jumpChain != null) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump server not found in provided chain: $id');
|
||||||
|
}
|
||||||
|
final fromStore = Stores.server.box.get(id);
|
||||||
|
if (fromStore == null) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump server not found: $id');
|
||||||
|
}
|
||||||
|
return fromStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _resolveMergedJumpChainInternal(target, resolveSpi: resolveSpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Spi> _resolveMergedJumpChainInternal(
|
||||||
|
Spi target, {
|
||||||
|
required Spi Function(String id) resolveSpi,
|
||||||
|
}) {
|
||||||
|
final roots = target.jumpChainIds ?? (target.jumpId == null ? const <String>[] : [target.jumpId!]);
|
||||||
|
if (roots.isEmpty) return const <Spi>[];
|
||||||
|
|
||||||
|
final seen = <String>{};
|
||||||
|
final stack = <String>{};
|
||||||
|
final out = <Spi>[];
|
||||||
|
|
||||||
|
String normId(Spi spi) => spi.id.isNotEmpty ? spi.id : spi.oldId;
|
||||||
|
|
||||||
|
void dfs(String id) {
|
||||||
|
final hop = resolveSpi(id);
|
||||||
|
final norm = normId(hop);
|
||||||
|
|
||||||
|
if (stack.contains(norm)) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump loop detected at $norm');
|
||||||
|
}
|
||||||
|
if (seen.contains(norm)) return;
|
||||||
|
|
||||||
|
stack.add(norm);
|
||||||
|
final deps = hop.jumpChainIds ?? (hop.jumpId == null ? const <String>[] : [hop.jumpId!]);
|
||||||
|
for (final dep in deps) {
|
||||||
|
dfs(dep);
|
||||||
|
}
|
||||||
|
stack.remove(norm);
|
||||||
|
|
||||||
|
if (seen.add(norm)) {
|
||||||
|
out.add(hop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final r in roots) {
|
||||||
|
dfs(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
Future<SSHClient> genClient(
|
Future<SSHClient> genClient(
|
||||||
Spi spi, {
|
Spi spi, {
|
||||||
void Function(GenSSHClientStatus)? onStatus,
|
void Function(GenSSHClientStatus)? onStatus,
|
||||||
@@ -41,46 +116,187 @@ Future<SSHClient> genClient(
|
|||||||
/// Only pass this param if using multi-threading and key login
|
/// Only pass this param if using multi-threading and key login
|
||||||
String? privateKey,
|
String? privateKey,
|
||||||
|
|
||||||
/// Only pass this param if using multi-threading and key login
|
/// Pre-resolved jump chain (in `spi.jumpId` order: immediate -> farthest).
|
||||||
String? jumpPrivateKey,
|
|
||||||
Duration timeout = const Duration(seconds: 5),
|
|
||||||
|
|
||||||
/// [Spi] of the jump server
|
|
||||||
///
|
///
|
||||||
/// Must pass this param if using multi-threading and key login
|
/// This is mainly used when `Stores` is unavailable (e.g. in an isolate).
|
||||||
Spi? jumpSpi,
|
List<Spi>? jumpChain,
|
||||||
|
|
||||||
|
/// Private keys for [jumpChain], aligned by index.
|
||||||
|
///
|
||||||
|
/// If a jump server uses key auth (`keyId != null`), you must provide the
|
||||||
|
/// decrypted key pem here (or `genClient` will try to read from `Stores`).
|
||||||
|
List<String?>? jumpPrivateKeys,
|
||||||
|
Duration timeout = const Duration(seconds: 5),
|
||||||
|
|
||||||
/// Handle keyboard-interactive authentication
|
/// Handle keyboard-interactive authentication
|
||||||
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
||||||
|
Map<String, String>? knownHostFingerprints,
|
||||||
|
void Function(String storageKey, String fingerprintHex)? onHostKeyAccepted,
|
||||||
|
Future<bool> Function(HostKeyPromptInfo info)? onHostKeyPrompt,
|
||||||
}) async {
|
}) async {
|
||||||
|
return _genClientInternal(
|
||||||
|
spi,
|
||||||
|
onStatus: onStatus,
|
||||||
|
privateKey: privateKey,
|
||||||
|
jumpChain: jumpChain,
|
||||||
|
jumpPrivateKeys: jumpPrivateKeys,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
knownHostFingerprints: knownHostFingerprints,
|
||||||
|
onHostKeyAccepted: onHostKeyAccepted,
|
||||||
|
onHostKeyPrompt: onHostKeyPrompt,
|
||||||
|
visited: <String>{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SSHClient> _genClientInternal(
|
||||||
|
Spi spi, {
|
||||||
|
void Function(GenSSHClientStatus)? onStatus,
|
||||||
|
String? privateKey,
|
||||||
|
List<Spi>? jumpChain,
|
||||||
|
List<String?>? jumpPrivateKeys,
|
||||||
|
Duration timeout = const Duration(seconds: 5),
|
||||||
|
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
||||||
|
Map<String, String>? knownHostFingerprints,
|
||||||
|
void Function(String storageKey, String fingerprintHex)? onHostKeyAccepted,
|
||||||
|
Future<bool> Function(HostKeyPromptInfo info)? onHostKeyPrompt,
|
||||||
|
required Set<String> visited,
|
||||||
|
SSHSocket? socketOverride,
|
||||||
|
bool followJumpConfig = true,
|
||||||
|
}) async {
|
||||||
|
final identifier = _hostIdentifier(spi);
|
||||||
|
if (!visited.add(identifier)) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump loop detected at ${spi.name} ($identifier)');
|
||||||
|
}
|
||||||
|
|
||||||
onStatus?.call(GenSSHClientStatus.socket);
|
onStatus?.call(GenSSHClientStatus.socket);
|
||||||
|
|
||||||
|
final hostKeyCache = Map<String, String>.from(knownHostFingerprints ?? _loadKnownHostFingerprints());
|
||||||
|
final hostKeyPersist = onHostKeyAccepted ?? _persistHostKeyFingerprint;
|
||||||
|
final hostKeyPrompt = onHostKeyPrompt ?? _defaultHostKeyPrompt;
|
||||||
|
|
||||||
String? alterUser;
|
String? alterUser;
|
||||||
|
|
||||||
final socket = await () async {
|
final (socket, hopClients) = await () async {
|
||||||
// Proxy
|
if (socketOverride != null) return (socketOverride, <SSHClient>[]);
|
||||||
final jumpSpi_ = () {
|
|
||||||
// Multi-thread or key login
|
|
||||||
if (jumpSpi != null) return jumpSpi;
|
|
||||||
// Main thread
|
|
||||||
if (spi.jumpId != null) return Stores.server.box.get(spi.jumpId);
|
|
||||||
}();
|
|
||||||
if (jumpSpi_ != null) {
|
|
||||||
final jumpClient = await genClient(jumpSpi_, privateKey: jumpPrivateKey, timeout: timeout);
|
|
||||||
|
|
||||||
return await jumpClient.forwardLocal(spi.ip, spi.port);
|
if (followJumpConfig) {
|
||||||
|
final injectedSpiMap = <String, Spi>{};
|
||||||
|
final injectedKeyMap = <String, String?>{};
|
||||||
|
|
||||||
|
if (jumpChain != null) {
|
||||||
|
for (var i = 0; i < jumpChain.length; i++) {
|
||||||
|
final s = jumpChain[i];
|
||||||
|
injectedSpiMap[s.id] = s;
|
||||||
|
if (s.oldId.isNotEmpty) injectedSpiMap[s.oldId] = s;
|
||||||
|
if (jumpPrivateKeys != null && i < jumpPrivateKeys.length) {
|
||||||
|
injectedKeyMap[s.id] = jumpPrivateKeys[i];
|
||||||
|
if (s.oldId.isNotEmpty) injectedKeyMap[s.oldId] = jumpPrivateKeys[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spi resolveSpi(String id) {
|
||||||
|
final injected = injectedSpiMap[id];
|
||||||
|
if (injected != null) return injected;
|
||||||
|
if (jumpChain != null) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump server not found in provided chain: $id');
|
||||||
|
}
|
||||||
|
final fromStore = Stores.server.box.get(id);
|
||||||
|
if (fromStore == null) {
|
||||||
|
throw SSHErr(type: SSHErrType.connect, message: 'Jump server not found: $id');
|
||||||
|
}
|
||||||
|
return fromStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? resolveHopPrivateKey(Spi hop) {
|
||||||
|
final keyId = hop.keyId;
|
||||||
|
if (keyId == null) return null;
|
||||||
|
final injected = injectedKeyMap[hop.id] ?? injectedKeyMap[hop.oldId];
|
||||||
|
return injected ?? getPrivateKey(keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final hops = _resolveMergedJumpChainInternal(spi, resolveSpi: resolveSpi);
|
||||||
|
if (hops.isNotEmpty) {
|
||||||
|
// Build multi-hop forward chain with dedup/merge.
|
||||||
|
final createdClients = <SSHClient>[];
|
||||||
|
SSHClient? currentClient;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final firstHop = hops.first;
|
||||||
|
final firstKey = resolveHopPrivateKey(firstHop);
|
||||||
|
if (firstHop.keyId != null && firstKey == null) {
|
||||||
|
throw SSHErr(type: SSHErrType.noPrivateKey, message: l10n.privateKeyNotFoundFmt(firstHop.keyId ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClient = await _genClientInternal(
|
||||||
|
firstHop,
|
||||||
|
privateKey: firstKey,
|
||||||
|
jumpChain: jumpChain,
|
||||||
|
jumpPrivateKeys: jumpPrivateKeys,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
knownHostFingerprints: hostKeyCache,
|
||||||
|
onHostKeyAccepted: hostKeyPersist,
|
||||||
|
onHostKeyPrompt: hostKeyPrompt,
|
||||||
|
visited: visited,
|
||||||
|
followJumpConfig: false,
|
||||||
|
);
|
||||||
|
createdClients.add(currentClient);
|
||||||
|
|
||||||
|
for (var i = 1; i < hops.length; i++) {
|
||||||
|
final hop = hops[i];
|
||||||
|
final forwarded = await currentClient!.forwardLocal(hop.ip, hop.port);
|
||||||
|
final hopKey = resolveHopPrivateKey(hop);
|
||||||
|
if (hop.keyId != null && hopKey == null) {
|
||||||
|
throw SSHErr(type: SSHErrType.noPrivateKey, message: l10n.privateKeyNotFoundFmt(hop.keyId ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClient = await _genClientInternal(
|
||||||
|
hop,
|
||||||
|
privateKey: hopKey,
|
||||||
|
jumpChain: jumpChain,
|
||||||
|
jumpPrivateKeys: jumpPrivateKeys,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
knownHostFingerprints: hostKeyCache,
|
||||||
|
onHostKeyAccepted: hostKeyPersist,
|
||||||
|
onHostKeyPrompt: hostKeyPrompt,
|
||||||
|
visited: visited,
|
||||||
|
socketOverride: forwarded,
|
||||||
|
followJumpConfig: false,
|
||||||
|
);
|
||||||
|
createdClients.add(currentClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
final forwardedSocket = await currentClient!.forwardLocal(spi.ip, spi.port);
|
||||||
|
return (forwardedSocket, createdClients);
|
||||||
|
} catch (e) {
|
||||||
|
// Close all created clients on error to avoid leaks
|
||||||
|
for (final client in createdClients) {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore close errors during cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
// Note: On success, all intermediate clients must remain open
|
||||||
|
// because the returned socket tunnels through them.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct
|
// Direct
|
||||||
try {
|
try {
|
||||||
return await SSHSocket.connect(spi.ip, spi.port, timeout: timeout);
|
return (await SSHSocket.connect(spi.ip, spi.port, timeout: timeout), <SSHClient>[]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Loggers.app.warning('genClient', e);
|
Loggers.app.warning('genClient', e);
|
||||||
if (spi.alterUrl == null) rethrow;
|
if (spi.alterUrl == null) rethrow;
|
||||||
try {
|
try {
|
||||||
final res = spi.parseAlterUrl();
|
final res = spi.parseAlterUrl();
|
||||||
alterUser = res.$2;
|
alterUser = res.$2;
|
||||||
return await SSHSocket.connect(res.$1, res.$3, timeout: timeout);
|
return (await SSHSocket.connect(res.$1, res.$3, timeout: timeout), <SSHClient>[]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Loggers.app.warning('genClient alterUrl', e);
|
Loggers.app.warning('genClient alterUrl', e);
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -88,28 +304,307 @@ Future<SSHClient> genClient(
|
|||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
|
||||||
final keyId = spi.keyId;
|
final hostKeyVerifier = _HostKeyVerifier(
|
||||||
if (keyId == null) {
|
spi: spi,
|
||||||
onStatus?.call(GenSSHClientStatus.pwd);
|
cache: hostKeyCache,
|
||||||
|
persistCallback: hostKeyPersist,
|
||||||
|
prompt: hostKeyPrompt,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<SSHClient> buildClient(SSHSocket socket) async {
|
||||||
|
final keyId = spi.keyId;
|
||||||
|
if (keyId == null) {
|
||||||
|
onStatus?.call(GenSSHClientStatus.pwd);
|
||||||
|
return SSHClient(
|
||||||
|
socket,
|
||||||
|
username: alterUser ?? spi.user,
|
||||||
|
onPasswordRequest: () => spi.pwd,
|
||||||
|
onUserInfoRequest: onKeyboardInteractive,
|
||||||
|
onVerifyHostKey: hostKeyVerifier.call,
|
||||||
|
// printDebug: debugPrint,
|
||||||
|
// printTrace: debugPrint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
privateKey ??= getPrivateKey(keyId);
|
||||||
|
|
||||||
|
onStatus?.call(GenSSHClientStatus.key);
|
||||||
return SSHClient(
|
return SSHClient(
|
||||||
socket,
|
socket,
|
||||||
username: alterUser ?? spi.user,
|
username: spi.user,
|
||||||
onPasswordRequest: () => spi.pwd,
|
// Must use [compute] here, instead of [Computer.shared.start]
|
||||||
|
identities: await compute(loadIndentity, privateKey!),
|
||||||
onUserInfoRequest: onKeyboardInteractive,
|
onUserInfoRequest: onKeyboardInteractive,
|
||||||
|
onVerifyHostKey: hostKeyVerifier.call,
|
||||||
// printDebug: debugPrint,
|
// printDebug: debugPrint,
|
||||||
// printTrace: debugPrint,
|
// printTrace: debugPrint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
privateKey ??= getPrivateKey(keyId);
|
|
||||||
|
|
||||||
onStatus?.call(GenSSHClientStatus.key);
|
final client = await buildClient(socket);
|
||||||
return SSHClient(
|
|
||||||
socket,
|
// Tie hop clients' lifetime to the final client: close all hop clients
|
||||||
username: spi.user,
|
// when the target client disconnects to avoid leaking SSH connections.
|
||||||
// Must use [compute] here, instead of [Computer.shared.start]
|
if (hopClients.isNotEmpty) {
|
||||||
identities: await compute(loadIndentity, privateKey),
|
client.done.whenComplete(() {
|
||||||
onUserInfoRequest: onKeyboardInteractive,
|
for (final hopClient in hopClients) {
|
||||||
// printDebug: debugPrint,
|
try {
|
||||||
// printTrace: debugPrint,
|
hopClient.close();
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore close errors during cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _HostKeyPersistCallback = void Function(String storageKey, String fingerprintHex);
|
||||||
|
|
||||||
|
class HostKeyPromptInfo {
|
||||||
|
HostKeyPromptInfo({
|
||||||
|
required this.spi,
|
||||||
|
required this.keyType,
|
||||||
|
required this.fingerprintHex,
|
||||||
|
required this.fingerprintBase64,
|
||||||
|
required this.isMismatch,
|
||||||
|
this.previousFingerprintHex,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Spi spi;
|
||||||
|
final String keyType;
|
||||||
|
final String fingerprintHex;
|
||||||
|
final String fingerprintBase64;
|
||||||
|
final bool isMismatch;
|
||||||
|
final String? previousFingerprintHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HostKeyVerifier {
|
||||||
|
_HostKeyVerifier({
|
||||||
|
required this.spi,
|
||||||
|
required Map<String, String> cache,
|
||||||
|
required this.prompt,
|
||||||
|
this.persistCallback,
|
||||||
|
}) : _cache = cache;
|
||||||
|
|
||||||
|
final Spi spi;
|
||||||
|
final Map<String, String> _cache;
|
||||||
|
final _HostKeyPersistCallback? persistCallback;
|
||||||
|
final Future<bool> Function(HostKeyPromptInfo info) prompt;
|
||||||
|
|
||||||
|
Future<bool> call(String keyType, Uint8List fingerprintBytes) async {
|
||||||
|
final storageKey = _hostKeyStorageKey(spi, keyType);
|
||||||
|
final fingerprintHex = _fingerprintToHex(fingerprintBytes);
|
||||||
|
final fingerprintBase64 = _fingerprintToBase64(fingerprintBytes);
|
||||||
|
final existing = _cache[storageKey];
|
||||||
|
|
||||||
|
if (existing == null) {
|
||||||
|
final accepted = await prompt(
|
||||||
|
HostKeyPromptInfo(
|
||||||
|
spi: spi,
|
||||||
|
keyType: keyType,
|
||||||
|
fingerprintHex: fingerprintHex,
|
||||||
|
fingerprintBase64: fingerprintBase64,
|
||||||
|
isMismatch: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!accepted) {
|
||||||
|
Loggers.app.warning('User rejected new SSH host key for ${spi.name} ($keyType).');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_cache[storageKey] = fingerprintHex;
|
||||||
|
persistCallback?.call(storageKey, fingerprintHex);
|
||||||
|
Loggers.app.info('Trusted SSH host key for ${spi.name} ($keyType).');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing == fingerprintHex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final accepted = await prompt(
|
||||||
|
HostKeyPromptInfo(
|
||||||
|
spi: spi,
|
||||||
|
keyType: keyType,
|
||||||
|
fingerprintHex: fingerprintHex,
|
||||||
|
fingerprintBase64: fingerprintBase64,
|
||||||
|
isMismatch: true,
|
||||||
|
previousFingerprintHex: existing,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!accepted) {
|
||||||
|
Loggers.app.warning(
|
||||||
|
'SSH host key mismatch for ${spi.name}',
|
||||||
|
'expected $existing but received $fingerprintHex ($keyType)',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache[storageKey] = fingerprintHex;
|
||||||
|
persistCallback?.call(storageKey, fingerprintHex);
|
||||||
|
Loggers.app.warning('Updated stored SSH host key for ${spi.name} ($keyType) after user confirmation.');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> _loadKnownHostFingerprints() {
|
||||||
|
try {
|
||||||
|
final prop = Stores.setting.sshKnownHostFingerprints;
|
||||||
|
return Map<String, String>.from(prop.get());
|
||||||
|
} catch (e, stack) {
|
||||||
|
Loggers.app.warning('Load SSH host key fingerprints failed', e, stack);
|
||||||
|
return <String, String>{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _persistHostKeyFingerprint(String storageKey, String fingerprintHex) {
|
||||||
|
try {
|
||||||
|
final prop = Stores.setting.sshKnownHostFingerprints;
|
||||||
|
final updated = Map<String, String>.from(prop.get());
|
||||||
|
if (updated[storageKey] == fingerprintHex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updated[storageKey] = fingerprintHex;
|
||||||
|
prop.put(updated);
|
||||||
|
Loggers.app.info('Stored SSH host key fingerprint for $storageKey');
|
||||||
|
} catch (e, stack) {
|
||||||
|
Loggers.app.warning('Persist SSH host key fingerprint failed', e, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _defaultHostKeyPrompt(HostKeyPromptInfo info) async {
|
||||||
|
final ctx = AppNavigator.context;
|
||||||
|
if (ctx == null) {
|
||||||
|
Loggers.app.warning('Host key prompt skipped: navigator context unavailable.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final hostLine = '${info.spi.user}@${info.spi.ip}:${info.spi.port}';
|
||||||
|
final description = info.isMismatch
|
||||||
|
? l10n.sshHostKeyChangedDesc(info.spi.name)
|
||||||
|
: l10n.sshHostKeyNewDesc(info.spi.name);
|
||||||
|
|
||||||
|
final result = await ctx.showRoundDialog<bool>(
|
||||||
|
title: libL10n.attention,
|
||||||
|
barrierDismiss: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(description),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SelectableText('${l10n.server}: ${info.spi.name}'),
|
||||||
|
SelectableText('${libL10n.addr}: $hostLine'),
|
||||||
|
SelectableText('${l10n.sshHostKeyType}: ${info.keyType}'),
|
||||||
|
SelectableText(l10n.sshHostKeyFingerprintMd5Hex(info.fingerprintHex)),
|
||||||
|
SelectableText(l10n.sshHostKeyFingerprintMd5Base64(info.fingerprintBase64)),
|
||||||
|
if (info.previousFingerprintHex != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SelectableText(l10n.sshHostKeyStoredFingerprint(info.previousFingerprintHex!)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => ctx.pop(false), child: Text(libL10n.cancel)),
|
||||||
|
TextButton(onPressed: () => ctx.pop(true), child: Text(libL10n.ok)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return result ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> ensureKnownHostKey(
|
||||||
|
Spi spi, {
|
||||||
|
Duration timeout = const Duration(seconds: 5),
|
||||||
|
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
||||||
|
}) async {
|
||||||
|
var cache = _loadKnownHostFingerprints();
|
||||||
|
|
||||||
|
final hops = resolveMergedJumpChain(spi);
|
||||||
|
|
||||||
|
// Check each hop's host key, routing through preceding hops
|
||||||
|
for (var i = 0; i < hops.length; i++) {
|
||||||
|
final hop = hops[i];
|
||||||
|
// Preceding hops needed to reach this hop
|
||||||
|
final precedingHops = i > 0 ? hops.sublist(0, i) : null;
|
||||||
|
final precedingKeys = precedingHops?.map((h) =>
|
||||||
|
h.keyId != null ? getPrivateKey(h.keyId!) : null
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
cache = await _ensureKnownHostKeyForSingle(
|
||||||
|
hop,
|
||||||
|
cache: cache,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
jumpChain: precedingHops,
|
||||||
|
jumpPrivateKeys: precedingKeys,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the target's host key, routing through all hops
|
||||||
|
final allKeys = hops.isNotEmpty
|
||||||
|
? hops.map((h) => h.keyId != null ? getPrivateKey(h.keyId!) : null).toList()
|
||||||
|
: null;
|
||||||
|
await _ensureKnownHostKeyForSingle(
|
||||||
|
spi,
|
||||||
|
cache: cache,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
jumpChain: hops.isNotEmpty ? hops : null,
|
||||||
|
jumpPrivateKeys: allKeys,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> _ensureKnownHostKeyForSingle(
|
||||||
|
Spi spi, {
|
||||||
|
required Map<String, String> cache,
|
||||||
|
Duration timeout = const Duration(seconds: 5),
|
||||||
|
SSHUserInfoRequestHandler? onKeyboardInteractive,
|
||||||
|
List<Spi>? jumpChain,
|
||||||
|
List<String?>? jumpPrivateKeys,
|
||||||
|
}) async {
|
||||||
|
if (_hasKnownHostFingerprintForSpi(spi, cache)) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
final client = await genClient(
|
||||||
|
spi,
|
||||||
|
timeout: timeout,
|
||||||
|
onKeyboardInteractive: onKeyboardInteractive,
|
||||||
|
knownHostFingerprints: cache,
|
||||||
|
jumpChain: jumpChain,
|
||||||
|
jumpPrivateKeys: jumpPrivateKeys,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.authenticated;
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.addAll(_loadKnownHostFingerprints());
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasKnownHostFingerprintForSpi(Spi spi, Map<String, String> cache) {
|
||||||
|
final prefix = '${_hostIdentifier(spi)}::';
|
||||||
|
return cache.keys.any((key) => key.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _hostKeyStorageKey(Spi spi, String keyType) {
|
||||||
|
final base = _hostIdentifier(spi);
|
||||||
|
return '$base::$keyType';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _hostIdentifier(Spi spi) => spi.id.isNotEmpty ? spi.id : spi.oldId;
|
||||||
|
|
||||||
|
String _fingerprintToHex(Uint8List fingerprint) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (var i = 0; i < fingerprint.length; i++) {
|
||||||
|
if (i > 0) buffer.write(':');
|
||||||
|
buffer.write(fingerprint[i].toRadixString(16).padLeft(2, '0'));
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _fingerprintToBase64(Uint8List fingerprint) => base64.encode(fingerprint);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ abstract final class SSHConfig {
|
|||||||
void addServer() {
|
void addServer() {
|
||||||
if (currentHost != null && currentHost != '*' && hostname != null) {
|
if (currentHost != null && currentHost != '*' && hostname != null) {
|
||||||
final spi = Spi(
|
final spi = Spi(
|
||||||
|
id: ShortId.generate(),
|
||||||
name: currentHost,
|
name: currentHost,
|
||||||
ip: hostname,
|
ip: hostname,
|
||||||
port: port,
|
port: port,
|
||||||
@@ -148,10 +149,12 @@ abstract final class SSHConfig {
|
|||||||
|
|
||||||
/// Extract jump host from ProxyJump or ProxyCommand
|
/// Extract jump host from ProxyJump or ProxyCommand
|
||||||
static String? _extractJumpHost(String value) {
|
static String? _extractJumpHost(String value) {
|
||||||
|
if (value.isEmpty) return null;
|
||||||
// For ProxyJump, the format is usually: user@host:port
|
// For ProxyJump, the format is usually: user@host:port
|
||||||
// For ProxyCommand, it's more complex and might need custom parsing
|
// For ProxyCommand, it's more complex and might need custom parsing
|
||||||
if (value.contains('@')) {
|
if (value.contains('@')) {
|
||||||
return value.split(' ').first;
|
final parts = value.split(' ');
|
||||||
|
return parts.isNotEmpty ? parts[0] : null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
66
lib/data/helper/ssh_decoder.dart
Normal file
66
lib/data/helper/ssh_decoder.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter_gbk2utf8/flutter_gbk2utf8.dart';
|
||||||
|
|
||||||
|
/// Utility class for decoding SSH command output with encoding fallback
|
||||||
|
class SSHDecoder {
|
||||||
|
/// Decodes bytes to string with multiple encoding fallback strategies
|
||||||
|
///
|
||||||
|
/// Tries in order:
|
||||||
|
/// 1. UTF-8 (with allowMalformed for lenient parsing)
|
||||||
|
/// - Windows PowerShell scripts now set UTF-8 output encoding by default
|
||||||
|
/// 2. GBK (for Windows Chinese systems)
|
||||||
|
/// - In some cases, Windows will still revert to GBK.
|
||||||
|
/// - Only attempted if UTF-8 produces replacement characters (<28>)
|
||||||
|
static String decode(
|
||||||
|
List<int> bytes, {
|
||||||
|
bool isWindows = false,
|
||||||
|
String? context,
|
||||||
|
}) {
|
||||||
|
if (bytes.isEmpty) return '';
|
||||||
|
|
||||||
|
// Try UTF-8 first with allowMalformed
|
||||||
|
try {
|
||||||
|
final result = utf8.decode(bytes, allowMalformed: true);
|
||||||
|
// Check if there are replacement characters indicating decode failure
|
||||||
|
// For non-Windows systems, always use UTF-8 result
|
||||||
|
if (!result.contains('<EFBFBD>') || !isWindows) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// For Windows with replacement chars, log and try GBK fallback
|
||||||
|
if (isWindows && result.contains('<EFBFBD>')) {
|
||||||
|
final contextInfo = context != null ? ' [$context]' : '';
|
||||||
|
Loggers.app.info('UTF-8 decode has replacement chars$contextInfo, trying GBK fallback');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
final contextInfo = context != null ? ' [$context]' : '';
|
||||||
|
Loggers.app.warning('UTF-8 decode failed$contextInfo: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Windows or when UTF-8 has replacement chars, try GBK
|
||||||
|
try {
|
||||||
|
return gbk.decode(bytes);
|
||||||
|
} catch (e) {
|
||||||
|
final contextInfo = context != null ? ' [$context]' : '';
|
||||||
|
Loggers.app.warning('GBK decode failed$contextInfo: $e');
|
||||||
|
// Return empty string if all decoding attempts fail
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes string to bytes for SSH command input
|
||||||
|
///
|
||||||
|
/// Uses GBK for Windows, UTF-8 for others
|
||||||
|
static List<int> encode(String text, {bool isWindows = false}) {
|
||||||
|
if (isWindows) {
|
||||||
|
try {
|
||||||
|
return gbk.encode(text);
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('GBK encode failed: $e, falling back to UTF-8');
|
||||||
|
return utf8.encode(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return utf8.encode(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:server_box/core/extension/ssh_client.dart';
|
||||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:server_box/data/model/server/system.dart';
|
import 'package:server_box/data/model/server/system.dart';
|
||||||
|
|
||||||
@@ -9,8 +10,8 @@ class SystemDetector {
|
|||||||
///
|
///
|
||||||
/// First checks if a custom system type is configured in [spi].
|
/// First checks if a custom system type is configured in [spi].
|
||||||
/// If not, attempts to detect the system by running commands:
|
/// If not, attempts to detect the system by running commands:
|
||||||
/// 1. 'ver' command to detect Windows
|
/// 1. 'uname -a' command to detect Linux/BSD/Darwin
|
||||||
/// 2. 'uname -a' command to detect Linux/BSD/Darwin
|
/// 2. 'ver' command to detect Windows (if uname fails)
|
||||||
///
|
///
|
||||||
/// Returns [SystemType.linux] as default if detection fails.
|
/// Returns [SystemType.linux] as default if detection fails.
|
||||||
static Future<SystemType> detect(SSHClient client, Spi spi) async {
|
static Future<SystemType> detect(SSHClient client, Spi spi) async {
|
||||||
@@ -22,17 +23,11 @@ class SystemDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to detect Windows systems first (more reliable detection)
|
// Try to detect Unix/Linux/BSD systems first (more reliable and doesn't create files)
|
||||||
final powershellResult = await client.run('ver 2>nul').string;
|
final unixResult = await client.runSafe(
|
||||||
if (powershellResult.isNotEmpty &&
|
'uname -a 2>/dev/null',
|
||||||
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
|
context: 'uname detection for ${spi.oldId}',
|
||||||
detectedSystemType = SystemType.windows;
|
);
|
||||||
dprint('Detected Windows system type for ${spi.oldId}');
|
|
||||||
return detectedSystemType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to detect Unix/Linux/BSD systems
|
|
||||||
final unixResult = await client.run('uname -a').string;
|
|
||||||
if (unixResult.contains('Linux')) {
|
if (unixResult.contains('Linux')) {
|
||||||
detectedSystemType = SystemType.linux;
|
detectedSystemType = SystemType.linux;
|
||||||
dprint('Detected Linux system type for ${spi.oldId}');
|
dprint('Detected Linux system type for ${spi.oldId}');
|
||||||
@@ -42,8 +37,21 @@ class SystemDetector {
|
|||||||
dprint('Detected BSD system type for ${spi.oldId}');
|
dprint('Detected BSD system type for ${spi.oldId}');
|
||||||
return detectedSystemType;
|
return detectedSystemType;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
Loggers.app.warning('System detection failed for ${spi.oldId}: $e');
|
// If uname fails, try to detect Windows systems
|
||||||
|
final powershellResult = await client.runSafe(
|
||||||
|
'ver 2>nul',
|
||||||
|
systemType: SystemType.windows,
|
||||||
|
context: 'ver detection for ${spi.oldId}',
|
||||||
|
);
|
||||||
|
if (powershellResult.isNotEmpty &&
|
||||||
|
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
|
||||||
|
detectedSystemType = SystemType.windows;
|
||||||
|
dprint('Detected Windows system type for ${spi.oldId}');
|
||||||
|
return detectedSystemType;
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
Loggers.app.warning('System detection failed for ${spi.oldId}: $e\n$stackTrace');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallback
|
// Default fallback
|
||||||
|
|||||||
74
lib/data/model/ai/ask_ai_models.dart
Normal file
74
lib/data/model/ai/ask_ai_models.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Chat message exchanged with the Ask AI service.
|
||||||
|
enum AskAiMessageRole { user, assistant }
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class AskAiMessage {
|
||||||
|
const AskAiMessage({
|
||||||
|
required this.role,
|
||||||
|
required this.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AskAiMessageRole role;
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
String get apiRole {
|
||||||
|
switch (role) {
|
||||||
|
case AskAiMessageRole.user:
|
||||||
|
return 'user';
|
||||||
|
case AskAiMessageRole.assistant:
|
||||||
|
return 'assistant';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recommended command returned by the AI tool call.
|
||||||
|
@immutable
|
||||||
|
class AskAiCommand {
|
||||||
|
const AskAiCommand({
|
||||||
|
required this.command,
|
||||||
|
this.description = '',
|
||||||
|
this.toolName,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String command;
|
||||||
|
final String description;
|
||||||
|
final String? toolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
sealed class AskAiEvent {
|
||||||
|
const AskAiEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Incremental text delta emitted while streaming the AI response.
|
||||||
|
class AskAiContentDelta extends AskAiEvent {
|
||||||
|
const AskAiContentDelta(this.delta);
|
||||||
|
final String delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits when a tool call returns a runnable command suggestion.
|
||||||
|
class AskAiToolSuggestion extends AskAiEvent {
|
||||||
|
const AskAiToolSuggestion(this.command);
|
||||||
|
final AskAiCommand command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals that the stream finished successfully.
|
||||||
|
class AskAiCompleted extends AskAiEvent {
|
||||||
|
const AskAiCompleted({
|
||||||
|
required this.fullText,
|
||||||
|
required this.commands,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String fullText;
|
||||||
|
final List<AskAiCommand> commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signals that the stream terminated with an error before completion.
|
||||||
|
class AskAiStreamError extends AskAiEvent {
|
||||||
|
const AskAiStreamError(this.error, this.stackTrace);
|
||||||
|
|
||||||
|
final Object error;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
}
|
||||||
@@ -55,9 +55,9 @@ abstract class BackupV2 with _$BackupV2 implements Mergeable {
|
|||||||
await Mergeable.mergeStore(backupData: history, store: Stores.history, force: force);
|
await Mergeable.mergeStore(backupData: history, store: Stores.history, force: force);
|
||||||
await Mergeable.mergeStore(backupData: settings, store: Stores.setting, force: force);
|
await Mergeable.mergeStore(backupData: settings, store: Stores.setting, force: force);
|
||||||
|
|
||||||
if (serverChanged) GlobalRef.gRef?.read(serversNotifierProvider.notifier).reload();
|
if (serverChanged) GlobalRef.gRef?.read(serversProvider.notifier).reload();
|
||||||
if (snippetChanged) GlobalRef.gRef?.read(snippetNotifierProvider.notifier).reload();
|
if (snippetChanged) GlobalRef.gRef?.read(snippetProvider.notifier).reload();
|
||||||
if (keyChanged) GlobalRef.gRef?.read(privateKeyNotifierProvider.notifier).reload();
|
if (keyChanged) GlobalRef.gRef?.read(privateKeyProvider.notifier).reload();
|
||||||
|
|
||||||
_loggerV2.info('Merge completed');
|
_loggerV2.info('Merge completed');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ class BackupService {
|
|||||||
await _confirmAndRestore(context, backup);
|
await _confirmAndRestore(context, backup);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
// Saved password failed, will prompt for manual input
|
Loggers.app.warning('Failed to restore with saved password, will prompt for manual input', e, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
119
lib/data/model/app/menu/platform.dart
Normal file
119
lib/data/model/app/menu/platform.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/model/app/tab.dart';
|
||||||
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
import 'package:server_box/data/res/url.dart';
|
||||||
|
import 'package:server_box/generated/l10n/l10n.dart';
|
||||||
|
import 'package:server_box/view/page/setting/entry.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
/// macOS Menu Bar
|
||||||
|
class MacOSMenuBarManager {
|
||||||
|
static List<PlatformMenu> buildMenuBar(BuildContext context, Function(int) onTabChanged) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final homeTabs = Stores.setting.homeTabs.fetch();
|
||||||
|
return [
|
||||||
|
PlatformMenu(
|
||||||
|
label: 'Server Box',
|
||||||
|
menus: [
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: libL10n.about,
|
||||||
|
onSelected: () => _showAboutDialog(context),
|
||||||
|
),
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: l10n.menuSettings,
|
||||||
|
shortcut: const SingleActivator(LogicalKeyboardKey.comma, meta: true),
|
||||||
|
onSelected: () => _openSettings(context),
|
||||||
|
),
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: l10n.menuQuit,
|
||||||
|
shortcut: const SingleActivator(LogicalKeyboardKey.keyQ, meta: true),
|
||||||
|
onSelected: () => SystemNavigator.pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PlatformMenu(
|
||||||
|
label: l10n.menuNavigate,
|
||||||
|
menus: _buildNavigateMenuItems(l10n, homeTabs, onTabChanged),
|
||||||
|
),
|
||||||
|
PlatformMenu(
|
||||||
|
label: l10n.menuInfo,
|
||||||
|
menus: [
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: l10n.menuGitHubRepository,
|
||||||
|
onSelected: () => _openURL(Urls.thisRepo),
|
||||||
|
),
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: l10n.menuWiki,
|
||||||
|
onSelected: () => _openURL(Urls.appWiki),
|
||||||
|
),
|
||||||
|
PlatformMenuItem(
|
||||||
|
label: l10n.menuHelp,
|
||||||
|
onSelected: () => _openURL(Urls.appHelp),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<PlatformMenuItem> _buildNavigateMenuItems(
|
||||||
|
AppLocalizations l10n,
|
||||||
|
List<AppTab> homeTabs,
|
||||||
|
Function(int) onTabChanged,
|
||||||
|
) {
|
||||||
|
final menuItems = <PlatformMenuItem>[];
|
||||||
|
final tabLabels = {
|
||||||
|
AppTab.server: l10n.server,
|
||||||
|
AppTab.ssh: 'SSH',
|
||||||
|
AppTab.file: libL10n.file,
|
||||||
|
AppTab.snippet: l10n.snippet,
|
||||||
|
};
|
||||||
|
for (var i = 0; i < homeTabs.length; i++) {
|
||||||
|
final tab = homeTabs[i];
|
||||||
|
final label = tabLabels[tab];
|
||||||
|
if (label == null) continue;
|
||||||
|
final shortcutKey = _getShortcutKeyForIndex(i);
|
||||||
|
menuItems.add(PlatformMenuItem(
|
||||||
|
label: label,
|
||||||
|
shortcut: shortcutKey != null
|
||||||
|
? SingleActivator(shortcutKey, meta: true)
|
||||||
|
: null,
|
||||||
|
onSelected: () => onTabChanged(i),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return menuItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LogicalKeyboardKey? _getShortcutKeyForIndex(int index) {
|
||||||
|
const keys = [
|
||||||
|
LogicalKeyboardKey.digit1,
|
||||||
|
LogicalKeyboardKey.digit2,
|
||||||
|
LogicalKeyboardKey.digit3,
|
||||||
|
LogicalKeyboardKey.digit4,
|
||||||
|
LogicalKeyboardKey.digit5,
|
||||||
|
LogicalKeyboardKey.digit6,
|
||||||
|
LogicalKeyboardKey.digit7,
|
||||||
|
LogicalKeyboardKey.digit8,
|
||||||
|
LogicalKeyboardKey.digit9,
|
||||||
|
];
|
||||||
|
return index < keys.length ? keys[index] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _showAboutDialog(BuildContext context) async {
|
||||||
|
const channel = MethodChannel('about');
|
||||||
|
await channel.invokeMethod('showAboutPanel');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _openSettings(BuildContext context) {
|
||||||
|
SettingsPage.route.go(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _openURL(String url) async {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,8 +55,8 @@ enum StatusCmdType implements ShellCmdType {
|
|||||||
uptime('uptime'),
|
uptime('uptime'),
|
||||||
conn('cat /proc/net/snmp'),
|
conn('cat /proc/net/snmp'),
|
||||||
disk(
|
disk(
|
||||||
'lsblk --bytes --json --output '
|
'(lsblk --bytes --json --output '
|
||||||
'FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID',
|
'FSTYPE,PATH,NAME,KNAME,MOUNTPOINT,FSSIZE,FSUSED,FSAVAIL,FSUSE%,UUID 2>/dev/null && echo "LSBLK_SUCCESS") || df -k'
|
||||||
),
|
),
|
||||||
mem("cat /proc/meminfo | grep -E 'Mem|Swap'"),
|
mem("cat /proc/meminfo | grep -E 'Mem|Swap'"),
|
||||||
tempType('cat /sys/class/thermal/thermal_zone*/type'),
|
tempType('cat /sys/class/thermal/thermal_zone*/type'),
|
||||||
@@ -166,25 +166,34 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
|||||||
echo('echo ${SystemType.windowsSign}'),
|
echo('echo ${SystemType.windowsSign}'),
|
||||||
time('[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()'),
|
time('[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()'),
|
||||||
|
|
||||||
/// Get network interface statistics using Windows Performance Counters
|
/// Get network interface statistics using WMI
|
||||||
///
|
///
|
||||||
/// Uses Get-Counter to collect network I/O metrics from all network interfaces:
|
/// Uses WMI Win32_PerfRawData_Tcpip_NetworkInterface for cross-language compatibility:
|
||||||
/// - Collects bytes received and sent per second for all network interfaces
|
|
||||||
/// - Takes 2 samples with 1 second interval to calculate rates
|
/// - Takes 2 samples with 1 second interval to calculate rates
|
||||||
/// - Outputs results in JSON format for easy parsing
|
|
||||||
/// - Counter paths use double backslashes to escape PowerShell string literals
|
|
||||||
net(
|
net(
|
||||||
r'Get-Counter -Counter '
|
r'$s1 = @(Get-WmiObject Win32_PerfRawData_Tcpip_NetworkInterface | '
|
||||||
r'"\\NetworkInterface(*)\\Bytes Received/sec", '
|
r'Select-Object Name, BytesReceivedPersec, BytesSentPersec, Timestamp_Sys100NS); '
|
||||||
r'"\\NetworkInterface(*)\\Bytes Sent/sec" '
|
r'Start-Sleep -Seconds 1; '
|
||||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
r'$s2 = @(Get-WmiObject Win32_PerfRawData_Tcpip_NetworkInterface | '
|
||||||
|
r'Select-Object Name, BytesReceivedPersec, BytesSentPersec, Timestamp_Sys100NS); '
|
||||||
|
r'@($s1, $s2) | ConvertTo-Json -Depth 5',
|
||||||
),
|
),
|
||||||
sys('(Get-ComputerInfo).OsName'),
|
sys('(Get-ComputerInfo).OsName'),
|
||||||
cpu(
|
cpu(
|
||||||
'Get-WmiObject -Class Win32_Processor | '
|
'Get-WmiObject -Class Win32_Processor | '
|
||||||
'Select-Object Name, LoadPercentage | ConvertTo-Json',
|
'Select-Object Name, LoadPercentage, NumberOfCores, NumberOfLogicalProcessors | ConvertTo-Json',
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Get system uptime by calculating time since last boot
|
||||||
|
///
|
||||||
|
/// Calculates uptime directly in PowerShell to avoid date format parsing issues:
|
||||||
|
/// - Gets LastBootUpTime from Win32_OperatingSystem
|
||||||
|
/// - Calculates difference from current time
|
||||||
|
/// - Returns pre-formatted string: "X days, H:MM" or "H:MM" (if less than 1 day)
|
||||||
|
/// - Uses ToString('00') for zero-padding to avoid quote escaping issues
|
||||||
|
uptime(
|
||||||
|
r"""$up = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime; if ($up.Days -gt 0) { "$($up.Days) days, $($up.Hours):$($up.Minutes.ToString('00'))" } else { "$($up.Hours):$($up.Minutes.ToString('00'))" }""",
|
||||||
),
|
),
|
||||||
uptime('(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime'),
|
|
||||||
conn('(netstat -an | findstr ESTABLISHED | Measure-Object -Line).Count'),
|
conn('(netstat -an | findstr ESTABLISHED | Measure-Object -Line).Count'),
|
||||||
disk(
|
disk(
|
||||||
'Get-WmiObject -Class Win32_LogicalDisk | '
|
'Get-WmiObject -Class Win32_LogicalDisk | '
|
||||||
@@ -213,19 +222,19 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
|||||||
),
|
),
|
||||||
host(r'Write-Output $env:COMPUTERNAME'),
|
host(r'Write-Output $env:COMPUTERNAME'),
|
||||||
|
|
||||||
/// Get disk I/O statistics using Windows Performance Counters
|
/// Get disk I/O statistics using WMI
|
||||||
///
|
///
|
||||||
/// Uses Get-Counter to collect disk I/O metrics from all physical disks:
|
/// Uses WMI Win32_PerfRawData_PerfDisk_PhysicalDisk:
|
||||||
/// - Monitors read and write bytes per second for all physical disks
|
/// - Monitors read and write bytes per second for all physical disks
|
||||||
/// - Takes 2 samples with 1 second interval to calculate I/O rates
|
/// - Takes 2 samples with 1 second interval to calculate rates
|
||||||
/// - Physical disk counters provide hardware-level I/O statistics
|
/// - DiskReadBytesPersec and DiskWriteBytesPersec are cumulative counters
|
||||||
/// - Outputs results in JSON format for parsing
|
|
||||||
/// - Counter names use wildcard (*) to capture all disk instances
|
|
||||||
diskio(
|
diskio(
|
||||||
r'Get-Counter -Counter '
|
r'$s1 = @(Get-WmiObject Win32_PerfRawData_PerfDisk_PhysicalDisk | '
|
||||||
r'"\\PhysicalDisk(*)\\Disk Read Bytes/sec", '
|
r'Select-Object Name, DiskReadBytesPersec, DiskWriteBytesPersec, Timestamp_Sys100NS); '
|
||||||
r'"\\PhysicalDisk(*)\\Disk Write Bytes/sec" '
|
r'Start-Sleep -Seconds 1; '
|
||||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
r'$s2 = @(Get-WmiObject Win32_PerfRawData_PerfDisk_PhysicalDisk | '
|
||||||
|
r'Select-Object Name, DiskReadBytesPersec, DiskWriteBytesPersec, Timestamp_Sys100NS); '
|
||||||
|
r'@($s1, $s2) | ConvertTo-Json -Depth 5',
|
||||||
),
|
),
|
||||||
battery(
|
battery(
|
||||||
'Get-WmiObject -Class Win32_Battery | '
|
'Get-WmiObject -Class Win32_Battery | '
|
||||||
@@ -287,7 +296,7 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
|||||||
String get separator => ScriptConstants.getCmdSeparator(name);
|
String get separator => ScriptConstants.getCmdSeparator(name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get divider => ScriptConstants.getCmdDivider(name);
|
String get divider => ScriptConstants.getWindowsCmdDivider(name);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CmdTypeSys get sysType => CmdTypeSys.windows;
|
CmdTypeSys get sysType => CmdTypeSys.windows;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ class ScriptConstants {
|
|||||||
/// Generate command-specific divider
|
/// Generate command-specific divider
|
||||||
static String getCmdDivider(String cmdName) => '\necho ${getCmdSeparator(cmdName)}\n\t';
|
static String getCmdDivider(String cmdName) => '\necho ${getCmdSeparator(cmdName)}\n\t';
|
||||||
|
|
||||||
|
/// Generate command-specific divider for Windows PowerShell
|
||||||
|
static String getWindowsCmdDivider(String cmdName) => '\n Write-Host "${getCmdSeparator(cmdName)}"\n ';
|
||||||
|
|
||||||
/// Parse script output into command-specific map
|
/// Parse script output into command-specific map
|
||||||
static Map<String, String> parseScriptOutput(String raw) {
|
static Map<String, String> parseScriptOutput(String raw) {
|
||||||
final result = <String, String>{};
|
final result = <String, String>{};
|
||||||
@@ -102,6 +105,7 @@ exec 2>/dev/null
|
|||||||
# DO NOT delete this file while app is running
|
# DO NOT delete this file while app is running
|
||||||
|
|
||||||
\$ErrorActionPreference = "SilentlyContinue"
|
\$ErrorActionPreference = "SilentlyContinue"
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
''';
|
''';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive_ce_flutter/adapters.dart';
|
||||||
import 'package:icons_plus/icons_plus.dart';
|
import 'package:icons_plus/icons_plus.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
import 'package:server_box/view/page/server/tab/tab.dart';
|
import 'package:server_box/view/page/server/tab/tab.dart';
|
||||||
@@ -8,10 +9,17 @@ import 'package:server_box/view/page/snippet/list.dart';
|
|||||||
import 'package:server_box/view/page/ssh/tab.dart';
|
import 'package:server_box/view/page/ssh/tab.dart';
|
||||||
import 'package:server_box/view/page/storage/local.dart';
|
import 'package:server_box/view/page/storage/local.dart';
|
||||||
|
|
||||||
|
part 'tab.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 103)
|
||||||
enum AppTab {
|
enum AppTab {
|
||||||
|
@HiveField(0)
|
||||||
server,
|
server,
|
||||||
|
@HiveField(1)
|
||||||
ssh,
|
ssh,
|
||||||
|
@HiveField(2)
|
||||||
file,
|
file,
|
||||||
|
@HiveField(3)
|
||||||
snippet
|
snippet
|
||||||
//settings,
|
//settings,
|
||||||
;
|
;
|
||||||
@@ -93,4 +101,35 @@ enum AppTab {
|
|||||||
static List<NavigationRailDestination> get navRailDestinations {
|
static List<NavigationRailDestination> get navRailDestinations {
|
||||||
return AppTab.values.map((e) => e.navRailDestination).toList();
|
return AppTab.values.map((e) => e.navRailDestination).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Helper function to parse AppTab list from stored object
|
||||||
|
static List<AppTab> parseAppTabsFromObj(dynamic val) {
|
||||||
|
if (val is List) {
|
||||||
|
final tabs = <AppTab>[];
|
||||||
|
for (final e in val) {
|
||||||
|
final tab = _parseAppTabFromElement(e);
|
||||||
|
if (tab != null) {
|
||||||
|
tabs.add(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tabs.isNotEmpty) return tabs;
|
||||||
|
}
|
||||||
|
return AppTab.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to parse a single AppTab from various element types
|
||||||
|
static AppTab? _parseAppTabFromElement(dynamic e) {
|
||||||
|
if (e is AppTab) {
|
||||||
|
return e;
|
||||||
|
} else if (e is String) {
|
||||||
|
return AppTab.values.firstWhereOrNull((t) => t.name == e);
|
||||||
|
} else if (e is int) {
|
||||||
|
if (e >= 0 && e < AppTab.values.length) {
|
||||||
|
return AppTab.values[e];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
lib/data/model/app/tab.g.dart
Normal file
52
lib/data/model/app/tab.g.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class AppTabAdapter extends TypeAdapter<AppTab> {
|
||||||
|
@override
|
||||||
|
final typeId = 103;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppTab read(BinaryReader reader) {
|
||||||
|
switch (reader.readByte()) {
|
||||||
|
case 0:
|
||||||
|
return AppTab.server;
|
||||||
|
case 1:
|
||||||
|
return AppTab.ssh;
|
||||||
|
case 2:
|
||||||
|
return AppTab.file;
|
||||||
|
case 3:
|
||||||
|
return AppTab.snippet;
|
||||||
|
default:
|
||||||
|
return AppTab.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, AppTab obj) {
|
||||||
|
switch (obj) {
|
||||||
|
case AppTab.server:
|
||||||
|
writer.writeByte(0);
|
||||||
|
case AppTab.ssh:
|
||||||
|
writer.writeByte(1);
|
||||||
|
case AppTab.file:
|
||||||
|
writer.writeByte(2);
|
||||||
|
case AppTab.snippet:
|
||||||
|
writer.writeByte(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is AppTabAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
@@ -37,12 +37,12 @@ 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: _asString(json['repository']),
|
||||||
tag: json['tag'],
|
tag: _asString(json['tag']),
|
||||||
id: json['Id'],
|
id: _asString(json['Id']),
|
||||||
created: json['Created'],
|
created: _asInt(json['Created']),
|
||||||
size: json['Size'],
|
size: _asInt(json['Size']),
|
||||||
containers: json['Containers'],
|
containers: _asInt(json['Containers']),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@@ -119,3 +119,16 @@ final class DockerImg implements ContainerImg {
|
|||||||
'Tag': tag,
|
'Tag': tag,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _asString(dynamic val) {
|
||||||
|
if (val == null) return null;
|
||||||
|
if (val is String) return val;
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _asInt(dynamic val) {
|
||||||
|
if (val == null) return null;
|
||||||
|
if (val is int) return val;
|
||||||
|
if (val is double) return val.toInt();
|
||||||
|
return int.tryParse(val.toString());
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
import 'package:server_box/data/model/container/status.dart';
|
||||||
import 'package:server_box/data/model/container/type.dart';
|
import 'package:server_box/data/model/container/type.dart';
|
||||||
import 'package:server_box/data/res/misc.dart';
|
import 'package:server_box/data/res/misc.dart';
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ sealed class ContainerPs {
|
|||||||
final String? image = null;
|
final String? image = null;
|
||||||
String? get name;
|
String? get name;
|
||||||
String? get cmd;
|
String? get cmd;
|
||||||
bool get running;
|
ContainerStatus get status;
|
||||||
|
|
||||||
String? cpu;
|
String? cpu;
|
||||||
String? mem;
|
String? mem;
|
||||||
@@ -19,7 +20,7 @@ sealed class ContainerPs {
|
|||||||
|
|
||||||
factory ContainerPs.fromRaw(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, [String? version]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PodmanPs implements ContainerPs {
|
final class PodmanPs implements ContainerPs {
|
||||||
@@ -51,10 +52,10 @@ final class PodmanPs implements ContainerPs {
|
|||||||
String? get cmd => command?.firstOrNull;
|
String? get cmd => command?.firstOrNull;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get running => exited != true;
|
ContainerStatus get status => ContainerStatus.fromPodmanExited(exited);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void parseStats(String s) {
|
void parseStats(String s, [String? version]) {
|
||||||
final stats = json.decode(s);
|
final stats = json.decode(s);
|
||||||
final cpuD = (stats['CPU'] as double? ?? 0).toStringAsFixed(1);
|
final cpuD = (stats['CPU'] as double? ?? 0).toStringAsFixed(1);
|
||||||
final cpuAvgD = (stats['AvgCPU'] as double? ?? 0).toStringAsFixed(1);
|
final cpuAvgD = (stats['AvgCPU'] as double? ?? 0).toStringAsFixed(1);
|
||||||
@@ -62,12 +63,32 @@ final class PodmanPs implements ContainerPs {
|
|||||||
final memLimit = (stats['MemLimit'] as int? ?? 0).bytes2Str;
|
final memLimit = (stats['MemLimit'] as int? ?? 0).bytes2Str;
|
||||||
final memUsage = (stats['MemUsage'] as int? ?? 0).bytes2Str;
|
final memUsage = (stats['MemUsage'] as int? ?? 0).bytes2Str;
|
||||||
mem = '$memUsage / $memLimit';
|
mem = '$memUsage / $memLimit';
|
||||||
final netIn = (stats['NetInput'] as int? ?? 0).bytes2Str;
|
|
||||||
final netOut = (stats['NetOutput'] as int? ?? 0).bytes2Str;
|
int netIn = 0;
|
||||||
net = '↓ $netIn / ↑ $netOut';
|
int netOut = 0;
|
||||||
|
final majorVersion = version?.split('.').firstOrNull;
|
||||||
|
final majorVersionNum = majorVersion != null ? int.tryParse(majorVersion) : null;
|
||||||
|
|
||||||
|
// Podman 4.x and earlier use top-level NetInput/NetOutput fields.
|
||||||
|
// Podman 5.x changed network backend (Netavark) and uses nested
|
||||||
|
// Network.{iface}.RxBytes/TxBytes structure instead.
|
||||||
|
if (majorVersionNum == null || majorVersionNum <= 4) {
|
||||||
|
netIn = stats['NetInput'] as int? ?? 0;
|
||||||
|
netOut = stats['NetOutput'] as int? ?? 0;
|
||||||
|
} else if (majorVersionNum >= 5) {
|
||||||
|
final network = stats['Network'] as Map<String, dynamic>?;
|
||||||
|
if (network != null) {
|
||||||
|
for (final interface in network.values) {
|
||||||
|
netIn += interface['RxBytes'] as int? ?? 0;
|
||||||
|
netOut += interface['TxBytes'] as int? ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
net = '↓ ${netIn.bytes2Str} / ↑ ${netOut.bytes2Str}';
|
||||||
|
|
||||||
final diskIn = (stats['BlockInput'] as int? ?? 0).bytes2Str;
|
final diskIn = (stats['BlockInput'] as int? ?? 0).bytes2Str;
|
||||||
final diskOut = (stats['BlockOutput'] as int? ?? 0).bytes2Str;
|
final diskOut = (stats['BlockOutput'] as int? ?? 0).bytes2Str;
|
||||||
disk = '${l10n.read} $diskOut / ${l10n.write} $diskIn';
|
disk = '${l10n.read} $diskIn / ${l10n.write} $diskOut';
|
||||||
}
|
}
|
||||||
|
|
||||||
factory PodmanPs.fromRawJson(String str) => PodmanPs.fromJson(json.decode(str));
|
factory PodmanPs.fromRawJson(String str) => PodmanPs.fromJson(json.decode(str));
|
||||||
@@ -121,18 +142,21 @@ final class DockerPs implements ContainerPs {
|
|||||||
String? get cmd => null;
|
String? get cmd => null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get running {
|
ContainerStatus get status => ContainerStatus.fromDockerState(state);
|
||||||
if (state?.contains('Exited') == true) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void parseStats(String s) {
|
void parseStats(String s, [String? version]) {
|
||||||
final stats = json.decode(s);
|
final stats = json.decode(s);
|
||||||
cpu = stats['CPUPerc'];
|
cpu = stats['CPUPerc'];
|
||||||
mem = stats['MemUsage'];
|
mem = stats['MemUsage'];
|
||||||
net = stats['NetIO'];
|
|
||||||
disk = stats['BlockIO'];
|
final netIO = stats['NetIO'] as String? ?? '0B / 0B';
|
||||||
|
final netParts = netIO.split(' / ');
|
||||||
|
net = '↓ ${netParts.firstOrNull ?? '0B'} / ↑ ${netParts.length > 1 ? netParts[1] : '0B'}';
|
||||||
|
|
||||||
|
final blockIO = stats['BlockIO'] as String? ?? '0B / 0B';
|
||||||
|
final blockParts = blockIO.split(' / ');
|
||||||
|
disk = '${l10n.read} ${blockParts.firstOrNull ?? '0B'} / ${l10n.write} ${blockParts.length > 1 ? blockParts[1] : '0B'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CONTAINER ID NAMES IMAGE STATUS
|
/// CONTAINER ID NAMES IMAGE STATUS
|
||||||
|
|||||||
70
lib/data/model/container/status.dart
Normal file
70
lib/data/model/container/status.dart
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
|
|
||||||
|
/// Represents the various states a container can be in.
|
||||||
|
/// Supports both Docker and Podman container status parsing.
|
||||||
|
enum ContainerStatus {
|
||||||
|
running,
|
||||||
|
exited,
|
||||||
|
created,
|
||||||
|
paused,
|
||||||
|
restarting,
|
||||||
|
removing,
|
||||||
|
dead,
|
||||||
|
unknown;
|
||||||
|
|
||||||
|
/// Check if the container is actively running
|
||||||
|
bool get isRunning => this == ContainerStatus.running;
|
||||||
|
|
||||||
|
/// Check if the container can be started
|
||||||
|
bool get canStart =>
|
||||||
|
this == ContainerStatus.exited ||
|
||||||
|
this == ContainerStatus.created ||
|
||||||
|
this == ContainerStatus.dead;
|
||||||
|
|
||||||
|
/// Check if the container can be stopped
|
||||||
|
bool get canStop =>
|
||||||
|
this == ContainerStatus.running || this == ContainerStatus.paused;
|
||||||
|
|
||||||
|
/// Check if the container can be restarted
|
||||||
|
bool get canRestart =>
|
||||||
|
this != ContainerStatus.removing && this != ContainerStatus.unknown;
|
||||||
|
|
||||||
|
/// Parse Docker container status string to ContainerStatus
|
||||||
|
static ContainerStatus fromDockerState(String? state) {
|
||||||
|
if (state == null || state.isEmpty) return ContainerStatus.unknown;
|
||||||
|
|
||||||
|
final lowerState = state.toLowerCase();
|
||||||
|
|
||||||
|
if (lowerState.startsWith('up')) return ContainerStatus.running;
|
||||||
|
if (lowerState.contains('exited')) return ContainerStatus.exited;
|
||||||
|
if (lowerState.contains('created')) return ContainerStatus.created;
|
||||||
|
if (lowerState.contains('paused')) return ContainerStatus.paused;
|
||||||
|
if (lowerState.contains('restarting')) return ContainerStatus.restarting;
|
||||||
|
if (lowerState.contains('removing')) return ContainerStatus.removing;
|
||||||
|
if (lowerState.contains('dead')) return ContainerStatus.dead;
|
||||||
|
|
||||||
|
return ContainerStatus.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse Podman container status from exited boolean
|
||||||
|
static ContainerStatus fromPodmanExited(bool? exited) {
|
||||||
|
if (exited == true) return ContainerStatus.exited;
|
||||||
|
if (exited == false) return ContainerStatus.running;
|
||||||
|
return ContainerStatus.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get display string for the status
|
||||||
|
String get displayName {
|
||||||
|
return switch (this) {
|
||||||
|
ContainerStatus.running => l10n.running,
|
||||||
|
ContainerStatus.exited => libL10n.exit,
|
||||||
|
ContainerStatus.created => 'Created',
|
||||||
|
ContainerStatus.paused => 'Paused',
|
||||||
|
ContainerStatus.restarting => 'Restarting',
|
||||||
|
ContainerStatus.removing => 'Removing',
|
||||||
|
ContainerStatus.dead => 'Dead',
|
||||||
|
ContainerStatus.unknown => libL10n.unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@ class UpgradePkgInfo {
|
|||||||
|
|
||||||
void _parsePacman(String raw) {
|
void _parsePacman(String raw) {
|
||||||
final parts = raw.split(' ');
|
final parts = raw.split(' ');
|
||||||
|
if (parts.length < 4) throw Exception('Invalid pacman output format');
|
||||||
package = parts[0];
|
package = parts[0];
|
||||||
nowVersion = parts[1];
|
nowVersion = parts[1];
|
||||||
newVersion = parts[3];
|
newVersion = parts[3];
|
||||||
@@ -70,6 +71,7 @@ class UpgradePkgInfo {
|
|||||||
|
|
||||||
void _parseOpkg(String raw) {
|
void _parseOpkg(String raw) {
|
||||||
final parts = raw.split(' - ');
|
final parts = raw.split(' - ');
|
||||||
|
if (parts.length < 3) throw Exception('Invalid opkg output format');
|
||||||
package = parts[0];
|
package = parts[0];
|
||||||
nowVersion = parts[1];
|
nowVersion = parts[1];
|
||||||
newVersion = parts[2];
|
newVersion = parts[2];
|
||||||
@@ -80,6 +82,7 @@ class UpgradePkgInfo {
|
|||||||
void _parseApk(String raw) {
|
void _parseApk(String raw) {
|
||||||
final parts = raw.split(' ');
|
final parts = raw.split(' ');
|
||||||
final len = parts.length;
|
final len = parts.length;
|
||||||
|
if (len < 2) throw Exception('Invalid apk output format');
|
||||||
newVersion = parts[len - 1];
|
newVersion = parts[len - 1];
|
||||||
nowVersion = parts[0];
|
nowVersion = parts[0];
|
||||||
newVersion = newVersion.substring(0, newVersion.length - 1);
|
newVersion = newVersion.substring(0, newVersion.length - 1);
|
||||||
|
|||||||
79
lib/data/model/server/connection_stat.dart
Normal file
79
lib/data/model/server/connection_stat.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:hive_ce/hive.dart';
|
||||||
|
|
||||||
|
part 'connection_stat.freezed.dart';
|
||||||
|
part 'connection_stat.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
@HiveType(typeId: 100)
|
||||||
|
abstract class ConnectionStat with _$ConnectionStat {
|
||||||
|
const factory ConnectionStat({
|
||||||
|
@HiveField(0) required String serverId,
|
||||||
|
@HiveField(1) required String serverName,
|
||||||
|
@HiveField(2) required DateTime timestamp,
|
||||||
|
@HiveField(3) required ConnectionResult result,
|
||||||
|
@HiveField(4) @Default('') String errorMessage,
|
||||||
|
@HiveField(5) required int durationMs,
|
||||||
|
}) = _ConnectionStat;
|
||||||
|
|
||||||
|
factory ConnectionStat.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ConnectionStatFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
@HiveType(typeId: 101)
|
||||||
|
abstract class ServerConnectionStats with _$ServerConnectionStats {
|
||||||
|
const factory ServerConnectionStats({
|
||||||
|
@HiveField(0) required String serverId,
|
||||||
|
@HiveField(1) required String serverName,
|
||||||
|
@HiveField(2) required int totalAttempts,
|
||||||
|
@HiveField(3) required int successCount,
|
||||||
|
@HiveField(4) required int failureCount,
|
||||||
|
@HiveField(5) @Default(null) DateTime? lastSuccessTime,
|
||||||
|
@HiveField(6) @Default(null) DateTime? lastFailureTime,
|
||||||
|
@HiveField(7) @Default([]) List<ConnectionStat> recentConnections,
|
||||||
|
@HiveField(8) required double successRate,
|
||||||
|
}) = _ServerConnectionStats;
|
||||||
|
|
||||||
|
factory ServerConnectionStats.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ServerConnectionStatsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HiveType(typeId: 102)
|
||||||
|
enum ConnectionResult {
|
||||||
|
@HiveField(0)
|
||||||
|
@JsonValue('success')
|
||||||
|
success,
|
||||||
|
@HiveField(1)
|
||||||
|
@JsonValue('timeout')
|
||||||
|
timeout,
|
||||||
|
@HiveField(2)
|
||||||
|
@JsonValue('auth_failed')
|
||||||
|
authFailed,
|
||||||
|
@HiveField(3)
|
||||||
|
@JsonValue('network_error')
|
||||||
|
networkError,
|
||||||
|
@HiveField(4)
|
||||||
|
@JsonValue('unknown_error')
|
||||||
|
unknownError,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConnectionResultExtension on ConnectionResult {
|
||||||
|
String get displayName {
|
||||||
|
switch (this) {
|
||||||
|
case ConnectionResult.success:
|
||||||
|
return libL10n.success;
|
||||||
|
case ConnectionResult.timeout:
|
||||||
|
return '${libL10n.error}(timeout)';
|
||||||
|
case ConnectionResult.authFailed:
|
||||||
|
return '${libL10n.error}(auth)';
|
||||||
|
case ConnectionResult.networkError:
|
||||||
|
return '${libL10n.error}(${libL10n.network})';
|
||||||
|
case ConnectionResult.unknownError:
|
||||||
|
return '${libL10n.error}(${libL10n.unknown})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isSuccess => this == ConnectionResult.success;
|
||||||
|
}
|
||||||
585
lib/data/model/server/connection_stat.freezed.dart
Normal file
585
lib/data/model/server/connection_stat.freezed.dart
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'connection_stat.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ConnectionStat {
|
||||||
|
|
||||||
|
@HiveField(0) String get serverId;@HiveField(1) String get serverName;@HiveField(2) DateTime get timestamp;@HiveField(3) ConnectionResult get result;@HiveField(4) String get errorMessage;@HiveField(5) int get durationMs;
|
||||||
|
/// Create a copy of ConnectionStat
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$ConnectionStatCopyWith<ConnectionStat> get copyWith => _$ConnectionStatCopyWithImpl<ConnectionStat>(this as ConnectionStat, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this ConnectionStat to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is ConnectionStat&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.serverName, serverName) || other.serverName == serverName)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.result, result) || other.result == result)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,serverId,serverName,timestamp,result,errorMessage,durationMs);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ConnectionStat(serverId: $serverId, serverName: $serverName, timestamp: $timestamp, result: $result, errorMessage: $errorMessage, durationMs: $durationMs)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $ConnectionStatCopyWith<$Res> {
|
||||||
|
factory $ConnectionStatCopyWith(ConnectionStat value, $Res Function(ConnectionStat) _then) = _$ConnectionStatCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@HiveField(0) String serverId,@HiveField(1) String serverName,@HiveField(2) DateTime timestamp,@HiveField(3) ConnectionResult result,@HiveField(4) String errorMessage,@HiveField(5) int durationMs
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$ConnectionStatCopyWithImpl<$Res>
|
||||||
|
implements $ConnectionStatCopyWith<$Res> {
|
||||||
|
_$ConnectionStatCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final ConnectionStat _self;
|
||||||
|
final $Res Function(ConnectionStat) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ConnectionStat
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? serverId = null,Object? serverName = null,Object? timestamp = null,Object? result = null,Object? errorMessage = null,Object? durationMs = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,serverName: null == serverName ? _self.serverName : serverName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,timestamp: null == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,result: null == result ? _self.result : result // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ConnectionResult,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [ConnectionStat].
|
||||||
|
extension ConnectionStatPatterns on ConnectionStat {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ConnectionStat value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ConnectionStat value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ConnectionStat value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) DateTime timestamp, @HiveField(3) ConnectionResult result, @HiveField(4) String errorMessage, @HiveField(5) int durationMs)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat() when $default != null:
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.timestamp,_that.result,_that.errorMessage,_that.durationMs);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) DateTime timestamp, @HiveField(3) ConnectionResult result, @HiveField(4) String errorMessage, @HiveField(5) int durationMs) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat():
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.timestamp,_that.result,_that.errorMessage,_that.durationMs);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) DateTime timestamp, @HiveField(3) ConnectionResult result, @HiveField(4) String errorMessage, @HiveField(5) int durationMs)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ConnectionStat() when $default != null:
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.timestamp,_that.result,_that.errorMessage,_that.durationMs);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _ConnectionStat implements ConnectionStat {
|
||||||
|
const _ConnectionStat({@HiveField(0) required this.serverId, @HiveField(1) required this.serverName, @HiveField(2) required this.timestamp, @HiveField(3) required this.result, @HiveField(4) this.errorMessage = '', @HiveField(5) required this.durationMs});
|
||||||
|
factory _ConnectionStat.fromJson(Map<String, dynamic> json) => _$ConnectionStatFromJson(json);
|
||||||
|
|
||||||
|
@override@HiveField(0) final String serverId;
|
||||||
|
@override@HiveField(1) final String serverName;
|
||||||
|
@override@HiveField(2) final DateTime timestamp;
|
||||||
|
@override@HiveField(3) final ConnectionResult result;
|
||||||
|
@override@JsonKey()@HiveField(4) final String errorMessage;
|
||||||
|
@override@HiveField(5) final int durationMs;
|
||||||
|
|
||||||
|
/// Create a copy of ConnectionStat
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$ConnectionStatCopyWith<_ConnectionStat> get copyWith => __$ConnectionStatCopyWithImpl<_ConnectionStat>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$ConnectionStatToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ConnectionStat&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.serverName, serverName) || other.serverName == serverName)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.result, result) || other.result == result)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,serverId,serverName,timestamp,result,errorMessage,durationMs);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ConnectionStat(serverId: $serverId, serverName: $serverName, timestamp: $timestamp, result: $result, errorMessage: $errorMessage, durationMs: $durationMs)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$ConnectionStatCopyWith<$Res> implements $ConnectionStatCopyWith<$Res> {
|
||||||
|
factory _$ConnectionStatCopyWith(_ConnectionStat value, $Res Function(_ConnectionStat) _then) = __$ConnectionStatCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@HiveField(0) String serverId,@HiveField(1) String serverName,@HiveField(2) DateTime timestamp,@HiveField(3) ConnectionResult result,@HiveField(4) String errorMessage,@HiveField(5) int durationMs
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$ConnectionStatCopyWithImpl<$Res>
|
||||||
|
implements _$ConnectionStatCopyWith<$Res> {
|
||||||
|
__$ConnectionStatCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _ConnectionStat _self;
|
||||||
|
final $Res Function(_ConnectionStat) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ConnectionStat
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? serverId = null,Object? serverName = null,Object? timestamp = null,Object? result = null,Object? errorMessage = null,Object? durationMs = null,}) {
|
||||||
|
return _then(_ConnectionStat(
|
||||||
|
serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,serverName: null == serverName ? _self.serverName : serverName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,timestamp: null == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,result: null == result ? _self.result : result // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ConnectionResult,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ServerConnectionStats {
|
||||||
|
|
||||||
|
@HiveField(0) String get serverId;@HiveField(1) String get serverName;@HiveField(2) int get totalAttempts;@HiveField(3) int get successCount;@HiveField(4) int get failureCount;@HiveField(5) DateTime? get lastSuccessTime;@HiveField(6) DateTime? get lastFailureTime;@HiveField(7) List<ConnectionStat> get recentConnections;@HiveField(8) double get successRate;
|
||||||
|
/// Create a copy of ServerConnectionStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$ServerConnectionStatsCopyWith<ServerConnectionStats> get copyWith => _$ServerConnectionStatsCopyWithImpl<ServerConnectionStats>(this as ServerConnectionStats, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this ServerConnectionStats to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerConnectionStats&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.serverName, serverName) || other.serverName == serverName)&&(identical(other.totalAttempts, totalAttempts) || other.totalAttempts == totalAttempts)&&(identical(other.successCount, successCount) || other.successCount == successCount)&&(identical(other.failureCount, failureCount) || other.failureCount == failureCount)&&(identical(other.lastSuccessTime, lastSuccessTime) || other.lastSuccessTime == lastSuccessTime)&&(identical(other.lastFailureTime, lastFailureTime) || other.lastFailureTime == lastFailureTime)&&const DeepCollectionEquality().equals(other.recentConnections, recentConnections)&&(identical(other.successRate, successRate) || other.successRate == successRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,serverId,serverName,totalAttempts,successCount,failureCount,lastSuccessTime,lastFailureTime,const DeepCollectionEquality().hash(recentConnections),successRate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ServerConnectionStats(serverId: $serverId, serverName: $serverName, totalAttempts: $totalAttempts, successCount: $successCount, failureCount: $failureCount, lastSuccessTime: $lastSuccessTime, lastFailureTime: $lastFailureTime, recentConnections: $recentConnections, successRate: $successRate)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $ServerConnectionStatsCopyWith<$Res> {
|
||||||
|
factory $ServerConnectionStatsCopyWith(ServerConnectionStats value, $Res Function(ServerConnectionStats) _then) = _$ServerConnectionStatsCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
@HiveField(0) String serverId,@HiveField(1) String serverName,@HiveField(2) int totalAttempts,@HiveField(3) int successCount,@HiveField(4) int failureCount,@HiveField(5) DateTime? lastSuccessTime,@HiveField(6) DateTime? lastFailureTime,@HiveField(7) List<ConnectionStat> recentConnections,@HiveField(8) double successRate
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$ServerConnectionStatsCopyWithImpl<$Res>
|
||||||
|
implements $ServerConnectionStatsCopyWith<$Res> {
|
||||||
|
_$ServerConnectionStatsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final ServerConnectionStats _self;
|
||||||
|
final $Res Function(ServerConnectionStats) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ServerConnectionStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? serverId = null,Object? serverName = null,Object? totalAttempts = null,Object? successCount = null,Object? failureCount = null,Object? lastSuccessTime = freezed,Object? lastFailureTime = freezed,Object? recentConnections = null,Object? successRate = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,serverName: null == serverName ? _self.serverName : serverName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,totalAttempts: null == totalAttempts ? _self.totalAttempts : totalAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,successCount: null == successCount ? _self.successCount : successCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,failureCount: null == failureCount ? _self.failureCount : failureCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,lastSuccessTime: freezed == lastSuccessTime ? _self.lastSuccessTime : lastSuccessTime // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,lastFailureTime: freezed == lastFailureTime ? _self.lastFailureTime : lastFailureTime // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,recentConnections: null == recentConnections ? _self.recentConnections : recentConnections // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ConnectionStat>,successRate: null == successRate ? _self.successRate : successRate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [ServerConnectionStats].
|
||||||
|
extension ServerConnectionStatsPatterns on ServerConnectionStats {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ServerConnectionStats value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ServerConnectionStats value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ServerConnectionStats value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) int totalAttempts, @HiveField(3) int successCount, @HiveField(4) int failureCount, @HiveField(5) DateTime? lastSuccessTime, @HiveField(6) DateTime? lastFailureTime, @HiveField(7) List<ConnectionStat> recentConnections, @HiveField(8) double successRate)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats() when $default != null:
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.totalAttempts,_that.successCount,_that.failureCount,_that.lastSuccessTime,_that.lastFailureTime,_that.recentConnections,_that.successRate);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) int totalAttempts, @HiveField(3) int successCount, @HiveField(4) int failureCount, @HiveField(5) DateTime? lastSuccessTime, @HiveField(6) DateTime? lastFailureTime, @HiveField(7) List<ConnectionStat> recentConnections, @HiveField(8) double successRate) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats():
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.totalAttempts,_that.successCount,_that.failureCount,_that.lastSuccessTime,_that.lastFailureTime,_that.recentConnections,_that.successRate);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@HiveField(0) String serverId, @HiveField(1) String serverName, @HiveField(2) int totalAttempts, @HiveField(3) int successCount, @HiveField(4) int failureCount, @HiveField(5) DateTime? lastSuccessTime, @HiveField(6) DateTime? lastFailureTime, @HiveField(7) List<ConnectionStat> recentConnections, @HiveField(8) double successRate)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _ServerConnectionStats() when $default != null:
|
||||||
|
return $default(_that.serverId,_that.serverName,_that.totalAttempts,_that.successCount,_that.failureCount,_that.lastSuccessTime,_that.lastFailureTime,_that.recentConnections,_that.successRate);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _ServerConnectionStats implements ServerConnectionStats {
|
||||||
|
const _ServerConnectionStats({@HiveField(0) required this.serverId, @HiveField(1) required this.serverName, @HiveField(2) required this.totalAttempts, @HiveField(3) required this.successCount, @HiveField(4) required this.failureCount, @HiveField(5) this.lastSuccessTime = null, @HiveField(6) this.lastFailureTime = null, @HiveField(7) final List<ConnectionStat> recentConnections = const [], @HiveField(8) required this.successRate}): _recentConnections = recentConnections;
|
||||||
|
factory _ServerConnectionStats.fromJson(Map<String, dynamic> json) => _$ServerConnectionStatsFromJson(json);
|
||||||
|
|
||||||
|
@override@HiveField(0) final String serverId;
|
||||||
|
@override@HiveField(1) final String serverName;
|
||||||
|
@override@HiveField(2) final int totalAttempts;
|
||||||
|
@override@HiveField(3) final int successCount;
|
||||||
|
@override@HiveField(4) final int failureCount;
|
||||||
|
@override@JsonKey()@HiveField(5) final DateTime? lastSuccessTime;
|
||||||
|
@override@JsonKey()@HiveField(6) final DateTime? lastFailureTime;
|
||||||
|
final List<ConnectionStat> _recentConnections;
|
||||||
|
@override@JsonKey()@HiveField(7) List<ConnectionStat> get recentConnections {
|
||||||
|
if (_recentConnections is EqualUnmodifiableListView) return _recentConnections;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_recentConnections);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override@HiveField(8) final double successRate;
|
||||||
|
|
||||||
|
/// Create a copy of ServerConnectionStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$ServerConnectionStatsCopyWith<_ServerConnectionStats> get copyWith => __$ServerConnectionStatsCopyWithImpl<_ServerConnectionStats>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$ServerConnectionStatsToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerConnectionStats&&(identical(other.serverId, serverId) || other.serverId == serverId)&&(identical(other.serverName, serverName) || other.serverName == serverName)&&(identical(other.totalAttempts, totalAttempts) || other.totalAttempts == totalAttempts)&&(identical(other.successCount, successCount) || other.successCount == successCount)&&(identical(other.failureCount, failureCount) || other.failureCount == failureCount)&&(identical(other.lastSuccessTime, lastSuccessTime) || other.lastSuccessTime == lastSuccessTime)&&(identical(other.lastFailureTime, lastFailureTime) || other.lastFailureTime == lastFailureTime)&&const DeepCollectionEquality().equals(other._recentConnections, _recentConnections)&&(identical(other.successRate, successRate) || other.successRate == successRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,serverId,serverName,totalAttempts,successCount,failureCount,lastSuccessTime,lastFailureTime,const DeepCollectionEquality().hash(_recentConnections),successRate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ServerConnectionStats(serverId: $serverId, serverName: $serverName, totalAttempts: $totalAttempts, successCount: $successCount, failureCount: $failureCount, lastSuccessTime: $lastSuccessTime, lastFailureTime: $lastFailureTime, recentConnections: $recentConnections, successRate: $successRate)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$ServerConnectionStatsCopyWith<$Res> implements $ServerConnectionStatsCopyWith<$Res> {
|
||||||
|
factory _$ServerConnectionStatsCopyWith(_ServerConnectionStats value, $Res Function(_ServerConnectionStats) _then) = __$ServerConnectionStatsCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
@HiveField(0) String serverId,@HiveField(1) String serverName,@HiveField(2) int totalAttempts,@HiveField(3) int successCount,@HiveField(4) int failureCount,@HiveField(5) DateTime? lastSuccessTime,@HiveField(6) DateTime? lastFailureTime,@HiveField(7) List<ConnectionStat> recentConnections,@HiveField(8) double successRate
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$ServerConnectionStatsCopyWithImpl<$Res>
|
||||||
|
implements _$ServerConnectionStatsCopyWith<$Res> {
|
||||||
|
__$ServerConnectionStatsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _ServerConnectionStats _self;
|
||||||
|
final $Res Function(_ServerConnectionStats) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ServerConnectionStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? serverId = null,Object? serverName = null,Object? totalAttempts = null,Object? successCount = null,Object? failureCount = null,Object? lastSuccessTime = freezed,Object? lastFailureTime = freezed,Object? recentConnections = null,Object? successRate = null,}) {
|
||||||
|
return _then(_ServerConnectionStats(
|
||||||
|
serverId: null == serverId ? _self.serverId : serverId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,serverName: null == serverName ? _self.serverName : serverName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,totalAttempts: null == totalAttempts ? _self.totalAttempts : totalAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,successCount: null == successCount ? _self.successCount : successCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,failureCount: null == failureCount ? _self.failureCount : failureCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,lastSuccessTime: freezed == lastSuccessTime ? _self.lastSuccessTime : lastSuccessTime // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,lastFailureTime: freezed == lastFailureTime ? _self.lastFailureTime : lastFailureTime // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,recentConnections: null == recentConnections ? _self._recentConnections : recentConnections // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ConnectionStat>,successRate: null == successRate ? _self.successRate : successRate // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
233
lib/data/model/server/connection_stat.g.dart
Normal file
233
lib/data/model/server/connection_stat.g.dart
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'connection_stat.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class ConnectionStatAdapter extends TypeAdapter<ConnectionStat> {
|
||||||
|
@override
|
||||||
|
final typeId = 100;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConnectionStat read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return ConnectionStat(
|
||||||
|
serverId: fields[0] as String,
|
||||||
|
serverName: fields[1] as String,
|
||||||
|
timestamp: fields[2] as DateTime,
|
||||||
|
result: fields[3] as ConnectionResult,
|
||||||
|
errorMessage: fields[4] == null ? '' : fields[4] as String,
|
||||||
|
durationMs: (fields[5] as num).toInt(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, ConnectionStat obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(6)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.serverId)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.serverName)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.timestamp)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.result)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.errorMessage)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is ConnectionStatAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerConnectionStatsAdapter extends TypeAdapter<ServerConnectionStats> {
|
||||||
|
@override
|
||||||
|
final typeId = 101;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ServerConnectionStats read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return ServerConnectionStats(
|
||||||
|
serverId: fields[0] as String,
|
||||||
|
serverName: fields[1] as String,
|
||||||
|
totalAttempts: (fields[2] as num).toInt(),
|
||||||
|
successCount: (fields[3] as num).toInt(),
|
||||||
|
failureCount: (fields[4] as num).toInt(),
|
||||||
|
lastSuccessTime: fields[5] == null ? null : fields[5] as DateTime?,
|
||||||
|
lastFailureTime: fields[6] == null ? null : fields[6] as DateTime?,
|
||||||
|
recentConnections: fields[7] == null
|
||||||
|
? []
|
||||||
|
: (fields[7] as List).cast<ConnectionStat>(),
|
||||||
|
successRate: (fields[8] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, ServerConnectionStats obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(9)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.serverId)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.serverName)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.totalAttempts)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.successCount)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.failureCount)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.lastSuccessTime)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.lastFailureTime)
|
||||||
|
..writeByte(7)
|
||||||
|
..write(obj.recentConnections)
|
||||||
|
..writeByte(8)
|
||||||
|
..write(obj.successRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is ServerConnectionStatsAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionResultAdapter extends TypeAdapter<ConnectionResult> {
|
||||||
|
@override
|
||||||
|
final typeId = 102;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConnectionResult read(BinaryReader reader) {
|
||||||
|
switch (reader.readByte()) {
|
||||||
|
case 0:
|
||||||
|
return ConnectionResult.success;
|
||||||
|
case 1:
|
||||||
|
return ConnectionResult.timeout;
|
||||||
|
case 2:
|
||||||
|
return ConnectionResult.authFailed;
|
||||||
|
case 3:
|
||||||
|
return ConnectionResult.networkError;
|
||||||
|
case 4:
|
||||||
|
return ConnectionResult.unknownError;
|
||||||
|
default:
|
||||||
|
return ConnectionResult.success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, ConnectionResult obj) {
|
||||||
|
switch (obj) {
|
||||||
|
case ConnectionResult.success:
|
||||||
|
writer.writeByte(0);
|
||||||
|
case ConnectionResult.timeout:
|
||||||
|
writer.writeByte(1);
|
||||||
|
case ConnectionResult.authFailed:
|
||||||
|
writer.writeByte(2);
|
||||||
|
case ConnectionResult.networkError:
|
||||||
|
writer.writeByte(3);
|
||||||
|
case ConnectionResult.unknownError:
|
||||||
|
writer.writeByte(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is ConnectionResultAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_ConnectionStat _$ConnectionStatFromJson(Map<String, dynamic> json) =>
|
||||||
|
_ConnectionStat(
|
||||||
|
serverId: json['serverId'] as String,
|
||||||
|
serverName: json['serverName'] as String,
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
result: $enumDecode(_$ConnectionResultEnumMap, json['result']),
|
||||||
|
errorMessage: json['errorMessage'] as String? ?? '',
|
||||||
|
durationMs: (json['durationMs'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ConnectionStatToJson(_ConnectionStat instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'serverId': instance.serverId,
|
||||||
|
'serverName': instance.serverName,
|
||||||
|
'timestamp': instance.timestamp.toIso8601String(),
|
||||||
|
'result': _$ConnectionResultEnumMap[instance.result]!,
|
||||||
|
'errorMessage': instance.errorMessage,
|
||||||
|
'durationMs': instance.durationMs,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ConnectionResultEnumMap = {
|
||||||
|
ConnectionResult.success: 'success',
|
||||||
|
ConnectionResult.timeout: 'timeout',
|
||||||
|
ConnectionResult.authFailed: 'auth_failed',
|
||||||
|
ConnectionResult.networkError: 'network_error',
|
||||||
|
ConnectionResult.unknownError: 'unknown_error',
|
||||||
|
};
|
||||||
|
|
||||||
|
_ServerConnectionStats _$ServerConnectionStatsFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _ServerConnectionStats(
|
||||||
|
serverId: json['serverId'] as String,
|
||||||
|
serverName: json['serverName'] as String,
|
||||||
|
totalAttempts: (json['totalAttempts'] as num).toInt(),
|
||||||
|
successCount: (json['successCount'] as num).toInt(),
|
||||||
|
failureCount: (json['failureCount'] as num).toInt(),
|
||||||
|
lastSuccessTime: json['lastSuccessTime'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['lastSuccessTime'] as String),
|
||||||
|
lastFailureTime: json['lastFailureTime'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['lastFailureTime'] as String),
|
||||||
|
recentConnections:
|
||||||
|
(json['recentConnections'] as List<dynamic>?)
|
||||||
|
?.map((e) => ConnectionStat.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
successRate: (json['successRate'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ServerConnectionStatsToJson(
|
||||||
|
_ServerConnectionStats instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'serverId': instance.serverId,
|
||||||
|
'serverName': instance.serverName,
|
||||||
|
'totalAttempts': instance.totalAttempts,
|
||||||
|
'successCount': instance.successCount,
|
||||||
|
'failureCount': instance.failureCount,
|
||||||
|
'lastSuccessTime': instance.lastSuccessTime?.toIso8601String(),
|
||||||
|
'lastFailureTime': instance.lastFailureTime?.toIso8601String(),
|
||||||
|
'recentConnections': instance.recentConnections,
|
||||||
|
'successRate': instance.successRate,
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@ import 'package:server_box/data/res/status.dart';
|
|||||||
/// Capacity of the FIFO queue
|
/// Capacity of the FIFO queue
|
||||||
const _kCap = 30;
|
const _kCap = 30;
|
||||||
|
|
||||||
class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
class Cpus extends TimeSeq<SingleCpuCore> {
|
||||||
Cpus(super.init1, super.init2);
|
Cpus(super.init1, super.init2);
|
||||||
|
|
||||||
final Map<String, int> brand = {};
|
final Map<String, int> brand = {};
|
||||||
@@ -14,21 +14,30 @@ class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
|||||||
@override
|
@override
|
||||||
void onUpdate() {
|
void onUpdate() {
|
||||||
_coresCount = now.length;
|
_coresCount = now.length;
|
||||||
|
if (pre.isEmpty || now.isEmpty || pre.length != now.length) {
|
||||||
|
_totalDelta = 0;
|
||||||
|
_user = 0;
|
||||||
|
_sys = 0;
|
||||||
|
_iowait = 0;
|
||||||
|
_idle = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_totalDelta = now[0].total - pre[0].total;
|
_totalDelta = now[0].total - pre[0].total;
|
||||||
_user = _getUser();
|
_user = _getUser();
|
||||||
_sys = _getSys();
|
_sys = _getSys();
|
||||||
_iowait = _getIowait();
|
_iowait = _getIowait();
|
||||||
_idle = _getIdle();
|
_idle = _getIdle();
|
||||||
_updateSpots();
|
_updateSpots();
|
||||||
//_updateRange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double usedPercent({int coreIdx = 0}) {
|
double usedPercent({int coreIdx = 0}) {
|
||||||
if (now.length != pre.length) return 0;
|
if (now.length != pre.length) return 0;
|
||||||
if (now.isEmpty) return 0;
|
if (now.isEmpty) return 0;
|
||||||
|
if (coreIdx >= now.length) return 0;
|
||||||
try {
|
try {
|
||||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||||
|
if (totalDelta == 0) return 0;
|
||||||
final used = idleDelta / totalDelta;
|
final used = idleDelta / totalDelta;
|
||||||
return used.isNaN ? 0 : 100 - used * 100;
|
return used.isNaN ? 0 : 100 - used * 100;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -157,6 +166,7 @@ class SingleCpuCore extends TimeSeqIface<SingleCpuCore> {
|
|||||||
final id = item.split(' ').firstOrNull;
|
final id = item.split(' ').firstOrNull;
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
final matches = item.replaceFirst(id, '').trim().split(' ');
|
final matches = item.replaceFirst(id, '').trim().split(' ');
|
||||||
|
if (matches.length < 7) continue;
|
||||||
cpus.add(
|
cpus.add(
|
||||||
SingleCpuCore(
|
SingleCpuCore(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom(
|
|||||||
|
|
||||||
Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
|
Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
if (instance.pveAddr case final value?) 'pveAddr': value,
|
'pveAddr': ?instance.pveAddr,
|
||||||
'pveIgnoreCert': instance.pveIgnoreCert,
|
'pveIgnoreCert': instance.pveIgnoreCert,
|
||||||
if (instance.cmds case final value?) 'cmds': value,
|
'cmds': ?instance.cmds,
|
||||||
if (instance.preferTempDev case final value?) 'preferTempDev': value,
|
'preferTempDev': ?instance.preferTempDev,
|
||||||
if (instance.logoUrl case final value?) 'logoUrl': value,
|
'logoUrl': ?instance.logoUrl,
|
||||||
if (instance.netDev case final value?) 'netDev': value,
|
'netDev': ?instance.netDev,
|
||||||
if (instance.scriptDir case final value?) 'scriptDir': value,
|
'scriptDir': ?instance.scriptDir,
|
||||||
};
|
};
|
||||||
|
|||||||
49
lib/data/model/server/discovery_result.dart
Normal file
49
lib/data/model/server/discovery_result.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'discovery_result.freezed.dart';
|
||||||
|
part 'discovery_result.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SshDiscoveryResult with _$SshDiscoveryResult {
|
||||||
|
const factory SshDiscoveryResult({
|
||||||
|
required String ip,
|
||||||
|
required int port,
|
||||||
|
String? banner,
|
||||||
|
@Default(false) bool isSelected,
|
||||||
|
}) = _SshDiscoveryResult;
|
||||||
|
|
||||||
|
factory SshDiscoveryResult.fromJson(Map<String, dynamic> json) => _$SshDiscoveryResultFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SshDiscoveryReport with _$SshDiscoveryReport {
|
||||||
|
const factory SshDiscoveryReport({
|
||||||
|
required String generatedAt,
|
||||||
|
required int durationMs,
|
||||||
|
required int count,
|
||||||
|
required List<SshDiscoveryResult> items,
|
||||||
|
}) = _SshDiscoveryReport;
|
||||||
|
|
||||||
|
factory SshDiscoveryReport.fromJson(Map<String, dynamic> json) => _$SshDiscoveryReportFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SshDiscoveryConfig with _$SshDiscoveryConfig {
|
||||||
|
const factory SshDiscoveryConfig({
|
||||||
|
@Default(700) int timeoutMs,
|
||||||
|
@Default(128) int maxConcurrency,
|
||||||
|
@Default(false) bool enableMdns,
|
||||||
|
@Default(4096) int hostEnumerationLimit,
|
||||||
|
}) = _SshDiscoveryConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SshDiscoveryConfigX on SshDiscoveryConfig {
|
||||||
|
List<String> toArgs() {
|
||||||
|
final args = <String>[];
|
||||||
|
args.add('--timeout-ms=$timeoutMs');
|
||||||
|
args.add('--max-concurrency=$maxConcurrency');
|
||||||
|
args.add('--host-enumeration-limit=$hostEnumerationLimit');
|
||||||
|
if (enableMdns) args.add('--enable-mdns');
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
}
|
||||||
830
lib/data/model/server/discovery_result.freezed.dart
Normal file
830
lib/data/model/server/discovery_result.freezed.dart
Normal file
@@ -0,0 +1,830 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'discovery_result.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SshDiscoveryResult {
|
||||||
|
|
||||||
|
String get ip; int get port; String? get banner; bool get isSelected;
|
||||||
|
/// Create a copy of SshDiscoveryResult
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SshDiscoveryResultCopyWith<SshDiscoveryResult> get copyWith => _$SshDiscoveryResultCopyWithImpl<SshDiscoveryResult>(this as SshDiscoveryResult, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SshDiscoveryResult to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryResult&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.banner, banner) || other.banner == banner)&&(identical(other.isSelected, isSelected) || other.isSelected == isSelected));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,ip,port,banner,isSelected);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryResult(ip: $ip, port: $port, banner: $banner, isSelected: $isSelected)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SshDiscoveryResultCopyWith<$Res> {
|
||||||
|
factory $SshDiscoveryResultCopyWith(SshDiscoveryResult value, $Res Function(SshDiscoveryResult) _then) = _$SshDiscoveryResultCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String ip, int port, String? banner, bool isSelected
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SshDiscoveryResultCopyWithImpl<$Res>
|
||||||
|
implements $SshDiscoveryResultCopyWith<$Res> {
|
||||||
|
_$SshDiscoveryResultCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SshDiscoveryResult _self;
|
||||||
|
final $Res Function(SshDiscoveryResult) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryResult
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? ip = null,Object? port = null,Object? banner = freezed,Object? isSelected = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,banner: freezed == banner ? _self.banner : banner // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isSelected: null == isSelected ? _self.isSelected : isSelected // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SshDiscoveryResult].
|
||||||
|
extension SshDiscoveryResultPatterns on SshDiscoveryResult {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryResult value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryResult value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryResult value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String ip, int port, String? banner, bool isSelected)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult() when $default != null:
|
||||||
|
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String ip, int port, String? banner, bool isSelected) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult():
|
||||||
|
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String ip, int port, String? banner, bool isSelected)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryResult() when $default != null:
|
||||||
|
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SshDiscoveryResult implements SshDiscoveryResult {
|
||||||
|
const _SshDiscoveryResult({required this.ip, required this.port, this.banner, this.isSelected = false});
|
||||||
|
factory _SshDiscoveryResult.fromJson(Map<String, dynamic> json) => _$SshDiscoveryResultFromJson(json);
|
||||||
|
|
||||||
|
@override final String ip;
|
||||||
|
@override final int port;
|
||||||
|
@override final String? banner;
|
||||||
|
@override@JsonKey() final bool isSelected;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryResult
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SshDiscoveryResultCopyWith<_SshDiscoveryResult> get copyWith => __$SshDiscoveryResultCopyWithImpl<_SshDiscoveryResult>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SshDiscoveryResultToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryResult&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.banner, banner) || other.banner == banner)&&(identical(other.isSelected, isSelected) || other.isSelected == isSelected));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,ip,port,banner,isSelected);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryResult(ip: $ip, port: $port, banner: $banner, isSelected: $isSelected)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SshDiscoveryResultCopyWith<$Res> implements $SshDiscoveryResultCopyWith<$Res> {
|
||||||
|
factory _$SshDiscoveryResultCopyWith(_SshDiscoveryResult value, $Res Function(_SshDiscoveryResult) _then) = __$SshDiscoveryResultCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String ip, int port, String? banner, bool isSelected
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SshDiscoveryResultCopyWithImpl<$Res>
|
||||||
|
implements _$SshDiscoveryResultCopyWith<$Res> {
|
||||||
|
__$SshDiscoveryResultCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SshDiscoveryResult _self;
|
||||||
|
final $Res Function(_SshDiscoveryResult) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryResult
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? ip = null,Object? port = null,Object? banner = freezed,Object? isSelected = null,}) {
|
||||||
|
return _then(_SshDiscoveryResult(
|
||||||
|
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,banner: freezed == banner ? _self.banner : banner // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,isSelected: null == isSelected ? _self.isSelected : isSelected // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SshDiscoveryReport {
|
||||||
|
|
||||||
|
String get generatedAt; int get durationMs; int get count; List<SshDiscoveryResult> get items;
|
||||||
|
/// Create a copy of SshDiscoveryReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SshDiscoveryReportCopyWith<SshDiscoveryReport> get copyWith => _$SshDiscoveryReportCopyWithImpl<SshDiscoveryReport>(this as SshDiscoveryReport, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SshDiscoveryReport to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryReport&&(identical(other.generatedAt, generatedAt) || other.generatedAt == generatedAt)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs)&&(identical(other.count, count) || other.count == count)&&const DeepCollectionEquality().equals(other.items, items));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,generatedAt,durationMs,count,const DeepCollectionEquality().hash(items));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryReport(generatedAt: $generatedAt, durationMs: $durationMs, count: $count, items: $items)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SshDiscoveryReportCopyWith<$Res> {
|
||||||
|
factory $SshDiscoveryReportCopyWith(SshDiscoveryReport value, $Res Function(SshDiscoveryReport) _then) = _$SshDiscoveryReportCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SshDiscoveryReportCopyWithImpl<$Res>
|
||||||
|
implements $SshDiscoveryReportCopyWith<$Res> {
|
||||||
|
_$SshDiscoveryReportCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SshDiscoveryReport _self;
|
||||||
|
final $Res Function(SshDiscoveryReport) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? generatedAt = null,Object? durationMs = null,Object? count = null,Object? items = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
generatedAt: null == generatedAt ? _self.generatedAt : generatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SshDiscoveryResult>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SshDiscoveryReport].
|
||||||
|
extension SshDiscoveryReportPatterns on SshDiscoveryReport {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryReport value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryReport value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryReport value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport() when $default != null:
|
||||||
|
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport():
|
||||||
|
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryReport() when $default != null:
|
||||||
|
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SshDiscoveryReport implements SshDiscoveryReport {
|
||||||
|
const _SshDiscoveryReport({required this.generatedAt, required this.durationMs, required this.count, required final List<SshDiscoveryResult> items}): _items = items;
|
||||||
|
factory _SshDiscoveryReport.fromJson(Map<String, dynamic> json) => _$SshDiscoveryReportFromJson(json);
|
||||||
|
|
||||||
|
@override final String generatedAt;
|
||||||
|
@override final int durationMs;
|
||||||
|
@override final int count;
|
||||||
|
final List<SshDiscoveryResult> _items;
|
||||||
|
@override List<SshDiscoveryResult> get items {
|
||||||
|
if (_items is EqualUnmodifiableListView) return _items;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SshDiscoveryReportCopyWith<_SshDiscoveryReport> get copyWith => __$SshDiscoveryReportCopyWithImpl<_SshDiscoveryReport>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SshDiscoveryReportToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryReport&&(identical(other.generatedAt, generatedAt) || other.generatedAt == generatedAt)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs)&&(identical(other.count, count) || other.count == count)&&const DeepCollectionEquality().equals(other._items, _items));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,generatedAt,durationMs,count,const DeepCollectionEquality().hash(_items));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryReport(generatedAt: $generatedAt, durationMs: $durationMs, count: $count, items: $items)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SshDiscoveryReportCopyWith<$Res> implements $SshDiscoveryReportCopyWith<$Res> {
|
||||||
|
factory _$SshDiscoveryReportCopyWith(_SshDiscoveryReport value, $Res Function(_SshDiscoveryReport) _then) = __$SshDiscoveryReportCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SshDiscoveryReportCopyWithImpl<$Res>
|
||||||
|
implements _$SshDiscoveryReportCopyWith<$Res> {
|
||||||
|
__$SshDiscoveryReportCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SshDiscoveryReport _self;
|
||||||
|
final $Res Function(_SshDiscoveryReport) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryReport
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? generatedAt = null,Object? durationMs = null,Object? count = null,Object? items = null,}) {
|
||||||
|
return _then(_SshDiscoveryReport(
|
||||||
|
generatedAt: null == generatedAt ? _self.generatedAt : generatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SshDiscoveryResult>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SshDiscoveryConfig {
|
||||||
|
|
||||||
|
int get timeoutMs; int get maxConcurrency; bool get enableMdns; int get hostEnumerationLimit;
|
||||||
|
/// Create a copy of SshDiscoveryConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SshDiscoveryConfigCopyWith<SshDiscoveryConfig> get copyWith => _$SshDiscoveryConfigCopyWithImpl<SshDiscoveryConfig>(this as SshDiscoveryConfig, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryConfig&&(identical(other.timeoutMs, timeoutMs) || other.timeoutMs == timeoutMs)&&(identical(other.maxConcurrency, maxConcurrency) || other.maxConcurrency == maxConcurrency)&&(identical(other.enableMdns, enableMdns) || other.enableMdns == enableMdns)&&(identical(other.hostEnumerationLimit, hostEnumerationLimit) || other.hostEnumerationLimit == hostEnumerationLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,timeoutMs,maxConcurrency,enableMdns,hostEnumerationLimit);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryConfig(timeoutMs: $timeoutMs, maxConcurrency: $maxConcurrency, enableMdns: $enableMdns, hostEnumerationLimit: $hostEnumerationLimit)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SshDiscoveryConfigCopyWith<$Res> {
|
||||||
|
factory $SshDiscoveryConfigCopyWith(SshDiscoveryConfig value, $Res Function(SshDiscoveryConfig) _then) = _$SshDiscoveryConfigCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SshDiscoveryConfigCopyWithImpl<$Res>
|
||||||
|
implements $SshDiscoveryConfigCopyWith<$Res> {
|
||||||
|
_$SshDiscoveryConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SshDiscoveryConfig _self;
|
||||||
|
final $Res Function(SshDiscoveryConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? timeoutMs = null,Object? maxConcurrency = null,Object? enableMdns = null,Object? hostEnumerationLimit = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
timeoutMs: null == timeoutMs ? _self.timeoutMs : timeoutMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,maxConcurrency: null == maxConcurrency ? _self.maxConcurrency : maxConcurrency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,enableMdns: null == enableMdns ? _self.enableMdns : enableMdns // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,hostEnumerationLimit: null == hostEnumerationLimit ? _self.hostEnumerationLimit : hostEnumerationLimit // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SshDiscoveryConfig].
|
||||||
|
extension SshDiscoveryConfigPatterns on SshDiscoveryConfig {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryConfig value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryConfig value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig():
|
||||||
|
return $default(_that);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryConfig value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig() when $default != null:
|
||||||
|
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig():
|
||||||
|
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
|
||||||
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SshDiscoveryConfig() when $default != null:
|
||||||
|
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _SshDiscoveryConfig implements SshDiscoveryConfig {
|
||||||
|
const _SshDiscoveryConfig({this.timeoutMs = 700, this.maxConcurrency = 128, this.enableMdns = false, this.hostEnumerationLimit = 4096});
|
||||||
|
|
||||||
|
|
||||||
|
@override@JsonKey() final int timeoutMs;
|
||||||
|
@override@JsonKey() final int maxConcurrency;
|
||||||
|
@override@JsonKey() final bool enableMdns;
|
||||||
|
@override@JsonKey() final int hostEnumerationLimit;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SshDiscoveryConfigCopyWith<_SshDiscoveryConfig> get copyWith => __$SshDiscoveryConfigCopyWithImpl<_SshDiscoveryConfig>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryConfig&&(identical(other.timeoutMs, timeoutMs) || other.timeoutMs == timeoutMs)&&(identical(other.maxConcurrency, maxConcurrency) || other.maxConcurrency == maxConcurrency)&&(identical(other.enableMdns, enableMdns) || other.enableMdns == enableMdns)&&(identical(other.hostEnumerationLimit, hostEnumerationLimit) || other.hostEnumerationLimit == hostEnumerationLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,timeoutMs,maxConcurrency,enableMdns,hostEnumerationLimit);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SshDiscoveryConfig(timeoutMs: $timeoutMs, maxConcurrency: $maxConcurrency, enableMdns: $enableMdns, hostEnumerationLimit: $hostEnumerationLimit)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SshDiscoveryConfigCopyWith<$Res> implements $SshDiscoveryConfigCopyWith<$Res> {
|
||||||
|
factory _$SshDiscoveryConfigCopyWith(_SshDiscoveryConfig value, $Res Function(_SshDiscoveryConfig) _then) = __$SshDiscoveryConfigCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SshDiscoveryConfigCopyWithImpl<$Res>
|
||||||
|
implements _$SshDiscoveryConfigCopyWith<$Res> {
|
||||||
|
__$SshDiscoveryConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SshDiscoveryConfig _self;
|
||||||
|
final $Res Function(_SshDiscoveryConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SshDiscoveryConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? timeoutMs = null,Object? maxConcurrency = null,Object? enableMdns = null,Object? hostEnumerationLimit = null,}) {
|
||||||
|
return _then(_SshDiscoveryConfig(
|
||||||
|
timeoutMs: null == timeoutMs ? _self.timeoutMs : timeoutMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,maxConcurrency: null == maxConcurrency ? _self.maxConcurrency : maxConcurrency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,enableMdns: null == enableMdns ? _self.enableMdns : enableMdns // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,hostEnumerationLimit: null == hostEnumerationLimit ? _self.hostEnumerationLimit : hostEnumerationLimit // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
41
lib/data/model/server/discovery_result.g.dart
Normal file
41
lib/data/model/server/discovery_result.g.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'discovery_result.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SshDiscoveryResult _$SshDiscoveryResultFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SshDiscoveryResult(
|
||||||
|
ip: json['ip'] as String,
|
||||||
|
port: (json['port'] as num).toInt(),
|
||||||
|
banner: json['banner'] as String?,
|
||||||
|
isSelected: json['isSelected'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SshDiscoveryResultToJson(_SshDiscoveryResult instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'ip': instance.ip,
|
||||||
|
'port': instance.port,
|
||||||
|
'banner': instance.banner,
|
||||||
|
'isSelected': instance.isSelected,
|
||||||
|
};
|
||||||
|
|
||||||
|
_SshDiscoveryReport _$SshDiscoveryReportFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SshDiscoveryReport(
|
||||||
|
generatedAt: json['generatedAt'] as String,
|
||||||
|
durationMs: (json['durationMs'] as num).toInt(),
|
||||||
|
count: (json['count'] as num).toInt(),
|
||||||
|
items: (json['items'] as List<dynamic>)
|
||||||
|
.map((e) => SshDiscoveryResult.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SshDiscoveryReportToJson(_SshDiscoveryReport instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'generatedAt': instance.generatedAt,
|
||||||
|
'durationMs': instance.durationMs,
|
||||||
|
'count': instance.count,
|
||||||
|
'items': instance.items,
|
||||||
|
};
|
||||||
@@ -44,22 +44,49 @@ class Disk with EquatableMixin {
|
|||||||
static List<Disk> parse(String raw) {
|
static List<Disk> parse(String raw) {
|
||||||
final list = <Disk>[];
|
final list = <Disk>[];
|
||||||
raw = raw.trim();
|
raw = raw.trim();
|
||||||
try {
|
|
||||||
if (raw.startsWith('{')) {
|
if (raw.isEmpty) {
|
||||||
// Parse JSON output from lsblk command
|
dprint('Empty disk info data received');
|
||||||
final Map<String, dynamic> jsonData = json.decode(raw);
|
return list;
|
||||||
final List<dynamic> blockdevices = jsonData['blockdevices'] ?? [];
|
}
|
||||||
|
|
||||||
for (final device in blockdevices) {
|
try {
|
||||||
// Process each device
|
// Check if we have lsblk JSON output with success marker
|
||||||
_processTopLevelDevice(device, list);
|
if (raw.startsWith('{')) {
|
||||||
|
// Extract JSON part (excluding the success marker if present)
|
||||||
|
final jsonEnd = raw.indexOf('\nLSBLK_SUCCESS');
|
||||||
|
final jsonPart = jsonEnd > 0 ? raw.substring(0, jsonEnd) : raw;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> jsonData = json.decode(jsonPart);
|
||||||
|
final List<dynamic> blockdevices = jsonData['blockdevices'] ?? [];
|
||||||
|
|
||||||
|
for (final device in blockdevices) {
|
||||||
|
// Process each device
|
||||||
|
_processTopLevelDevice(device, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we successfully parsed JSON and have valid disks, return them
|
||||||
|
if (list.isNotEmpty) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
Loggers.app.warning('JSON parsing failed, falling back to df -k output: $e');
|
||||||
|
} catch (e) {
|
||||||
|
Loggers.app.warning('Error processing JSON disk data, falling back to df -k output: $e', e);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Fallback to the old parsing method in case of non-JSON output
|
|
||||||
|
// Check if we have df -k output (fallback case)
|
||||||
|
if (raw.contains('Filesystem') && raw.contains('Mounted on')) {
|
||||||
return _parseWithOldMethod(raw);
|
return _parseWithOldMethod(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we reach here, both parsing methods failed
|
||||||
|
Loggers.app.warning('Unable to parse disk info with any method');
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Loggers.app.warning('Failed to parse disk info: $e', e);
|
Loggers.app.warning('Failed to parse disk info with both methods: $e', e);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -88,6 +115,32 @@ class Disk with EquatableMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse filesystem fields from device data
|
||||||
|
static ({BigInt size, BigInt used, BigInt avail, int usedPercent}) _parseFilesystemFields(Map<String, dynamic> device) {
|
||||||
|
// Helper function to parse size strings safely
|
||||||
|
BigInt parseSize(String? sizeStr) {
|
||||||
|
if (sizeStr == null || sizeStr.isEmpty || sizeStr == 'null' || sizeStr == '0') {
|
||||||
|
return BigInt.zero;
|
||||||
|
}
|
||||||
|
return (BigInt.tryParse(sizeStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to parse percentage strings
|
||||||
|
int parsePercent(String? percentStr) {
|
||||||
|
if (percentStr == null || percentStr.isEmpty || percentStr == 'null') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return int.tryParse(percentStr.replaceAll('%', '')) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
size: parseSize(device['fssize']?.toString()),
|
||||||
|
used: parseSize(device['fsused']?.toString()),
|
||||||
|
avail: parseSize(device['fsavail']?.toString()),
|
||||||
|
usedPercent: parsePercent(device['fsuse%']?.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a single device without recursively processing its children
|
/// Process a single device without recursively processing its children
|
||||||
static Disk? _processSingleDevice(Map<String, dynamic> device) {
|
static Disk? _processSingleDevice(Map<String, dynamic> device) {
|
||||||
final fstype = device['fstype']?.toString();
|
final fstype = device['fstype']?.toString();
|
||||||
@@ -102,20 +155,7 @@ class Disk with EquatableMixin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final sizeStr = device['fssize']?.toString() ?? '0';
|
final fsFields = _parseFilesystemFields(device);
|
||||||
final size = (BigInt.tryParse(sizeStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
final usedStr = device['fsused']?.toString() ?? '0';
|
|
||||||
final used = (BigInt.tryParse(usedStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
final availStr = device['fsavail']?.toString() ?? '0';
|
|
||||||
final avail = (BigInt.tryParse(availStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
// Parse fsuse% which is usually in the format "45%"
|
|
||||||
String usePercentStr = device['fsuse%']?.toString() ?? '0';
|
|
||||||
usePercentStr = usePercentStr.replaceAll('%', '');
|
|
||||||
final usedPercent = int.tryParse(usePercentStr) ?? 0;
|
|
||||||
|
|
||||||
final name = device['name']?.toString();
|
final name = device['name']?.toString();
|
||||||
final kname = device['kname']?.toString();
|
final kname = device['kname']?.toString();
|
||||||
final uuid = device['uuid']?.toString();
|
final uuid = device['uuid']?.toString();
|
||||||
@@ -124,10 +164,10 @@ class Disk with EquatableMixin {
|
|||||||
path: path,
|
path: path,
|
||||||
fsTyp: fstype,
|
fsTyp: fstype,
|
||||||
mount: mountpoint,
|
mount: mountpoint,
|
||||||
usedPercent: usedPercent,
|
usedPercent: fsFields.usedPercent,
|
||||||
used: used,
|
used: fsFields.used,
|
||||||
size: size,
|
size: fsFields.size,
|
||||||
avail: avail,
|
avail: fsFields.avail,
|
||||||
name: name,
|
name: name,
|
||||||
kname: kname,
|
kname: kname,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
@@ -155,20 +195,7 @@ class Disk with EquatableMixin {
|
|||||||
|
|
||||||
// Handle common filesystem cases or parent devices with children
|
// Handle common filesystem cases or parent devices with children
|
||||||
if ((fstype != null && _shouldCalc(fstype, mount)) || (childDisks.isNotEmpty && path.isNotEmpty)) {
|
if ((fstype != null && _shouldCalc(fstype, mount)) || (childDisks.isNotEmpty && path.isNotEmpty)) {
|
||||||
final sizeStr = device['fssize']?.toString() ?? '0';
|
final fsFields = _parseFilesystemFields(device);
|
||||||
final size = (BigInt.tryParse(sizeStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
final usedStr = device['fsused']?.toString() ?? '0';
|
|
||||||
final used = (BigInt.tryParse(usedStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
final availStr = device['fsavail']?.toString() ?? '0';
|
|
||||||
final avail = (BigInt.tryParse(availStr) ?? BigInt.zero) ~/ BigInt.from(1024);
|
|
||||||
|
|
||||||
// Parse fsuse% which is usually in the format "45%"
|
|
||||||
String usePercentStr = device['fsuse%']?.toString() ?? '0';
|
|
||||||
usePercentStr = usePercentStr.replaceAll('%', '');
|
|
||||||
final usedPercent = int.tryParse(usePercentStr) ?? 0;
|
|
||||||
|
|
||||||
final name = device['name']?.toString();
|
final name = device['name']?.toString();
|
||||||
final kname = device['kname']?.toString();
|
final kname = device['kname']?.toString();
|
||||||
final uuid = device['uuid']?.toString();
|
final uuid = device['uuid']?.toString();
|
||||||
@@ -177,10 +204,10 @@ class Disk with EquatableMixin {
|
|||||||
path: path,
|
path: path,
|
||||||
fsTyp: fstype,
|
fsTyp: fstype,
|
||||||
mount: mount,
|
mount: mount,
|
||||||
usedPercent: usedPercent,
|
usedPercent: fsFields.usedPercent,
|
||||||
used: used,
|
used: fsFields.used,
|
||||||
size: size,
|
size: fsFields.size,
|
||||||
avail: avail,
|
avail: fsFields.avail,
|
||||||
name: name,
|
name: name,
|
||||||
kname: kname,
|
kname: kname,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
@@ -253,7 +280,7 @@ class Disk with EquatableMixin {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiskIO extends TimeSeq<List<DiskIOPiece>> {
|
class DiskIO extends TimeSeq<DiskIOPiece> {
|
||||||
DiskIO(super.init1, super.init2);
|
DiskIO(super.init1, super.init2);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class NetSpeedPart extends TimeSeqIface<NetSpeedPart> {
|
|||||||
|
|
||||||
typedef CachedNetVals = ({String sizeIn, String sizeOut, String speedIn, String speedOut});
|
typedef CachedNetVals = ({String sizeIn, String sizeOut, String speedIn, String speedOut});
|
||||||
|
|
||||||
class NetSpeed extends TimeSeq<List<NetSpeedPart>> {
|
class NetSpeed extends TimeSeq<NetSpeedPart> {
|
||||||
NetSpeed(super.init1, super.init2);
|
NetSpeed(super.init1, super.init2);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -164,7 +164,8 @@ class NetSpeed extends TimeSeq<List<NetSpeedPart>> {
|
|||||||
final bytesIn = BigInt.parse(bytes.first);
|
final bytesIn = BigInt.parse(bytes.first);
|
||||||
final bytesOut = BigInt.parse(bytes[8]);
|
final bytesOut = BigInt.parse(bytes[8]);
|
||||||
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
||||||
} catch (_) {
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to parse net speed data: $item', e, s);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ class Proc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String get binary {
|
String get binary {
|
||||||
final parts = command.split(' ');
|
final parts = command.trim().split(' ').where((e) => e.isNotEmpty).toList();
|
||||||
return parts[0];
|
return parts.isNotEmpty ? parts[0] : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:server_box/data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
import 'package:server_box/data/model/server/custom.dart';
|
import 'package:server_box/data/model/server/custom.dart';
|
||||||
@@ -35,8 +36,15 @@ abstract class Spi with _$Spi {
|
|||||||
String? alterUrl,
|
String? alterUrl,
|
||||||
@Default(true) bool autoConnect,
|
@Default(true) bool autoConnect,
|
||||||
|
|
||||||
/// [id] of the jump server
|
/// [id] of the jump server (legacy, single hop)
|
||||||
|
///
|
||||||
|
/// Migrated to [jumpChainIds].
|
||||||
String? jumpId,
|
String? jumpId,
|
||||||
|
|
||||||
|
/// Jump chain hop ids (nearest -> farthest)
|
||||||
|
///
|
||||||
|
/// Preferred over [jumpId].
|
||||||
|
@JsonKey(includeIfNull: false) List<String>? jumpChainIds,
|
||||||
ServerCustom? custom,
|
ServerCustom? custom,
|
||||||
WakeOnLanCfg? wolCfg,
|
WakeOnLanCfg? wolCfg,
|
||||||
|
|
||||||
@@ -79,7 +87,10 @@ extension Spix on Spi {
|
|||||||
String? migrateId() {
|
String? migrateId() {
|
||||||
if (id.isNotEmpty) return null;
|
if (id.isNotEmpty) return null;
|
||||||
ServerStore.instance.delete(oldId);
|
ServerStore.instance.delete(oldId);
|
||||||
final newSpi = copyWith(id: ShortId.generate());
|
final newSpi = copyWith(
|
||||||
|
id: ShortId.generate(),
|
||||||
|
jumpChainIds: jumpChainIds ?? (jumpId == null ? null : [jumpId!]),
|
||||||
|
);
|
||||||
newSpi.save();
|
newSpi.save();
|
||||||
return newSpi.id;
|
return newSpi.id;
|
||||||
}
|
}
|
||||||
@@ -94,7 +105,8 @@ extension Spix on Spi {
|
|||||||
port == other.port &&
|
port == other.port &&
|
||||||
pwd == other.pwd &&
|
pwd == other.pwd &&
|
||||||
keyId == other.keyId &&
|
keyId == other.keyId &&
|
||||||
jumpId == other.jumpId;
|
jumpId == other.jumpId &&
|
||||||
|
listEquals(jumpChainIds, other.jumpChainIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the connection should be re-established.
|
/// Returns true if the connection should be re-established.
|
||||||
@@ -137,7 +149,7 @@ extension Spix on Spi {
|
|||||||
tags: ['tag1', 'tag2'],
|
tags: ['tag1', 'tag2'],
|
||||||
alterUrl: 'user@ip:port',
|
alterUrl: 'user@ip:port',
|
||||||
autoConnect: true,
|
autoConnect: true,
|
||||||
jumpId: 'jump_server_id',
|
jumpChainIds: ['jump_server_id'],
|
||||||
custom: ServerCustom(
|
custom: ServerCustom(
|
||||||
pveAddr: 'http://localhost:8006',
|
pveAddr: 'http://localhost:8006',
|
||||||
pveIgnoreCert: false,
|
pveIgnoreCert: false,
|
||||||
|
|||||||
@@ -16,8 +16,13 @@ T _$identity<T>(T value) => value;
|
|||||||
mixin _$Spi {
|
mixin _$Spi {
|
||||||
|
|
||||||
String get name; String get ip; int get port; String get user; String? get pwd;/// [id] of private key
|
String get name; String get ip; int get port; String get user; String? get pwd;/// [id] of private key
|
||||||
@JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server
|
@JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server (legacy, single hop)
|
||||||
String? get jumpId; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal.
|
///
|
||||||
|
/// Migrated to [jumpChainIds].
|
||||||
|
String? get jumpId;/// Jump chain hop ids (nearest -> farthest)
|
||||||
|
///
|
||||||
|
/// Preferred over [jumpId].
|
||||||
|
@JsonKey(includeIfNull: false) List<String>? get jumpChainIds; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal.
|
||||||
Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;/// Custom system type (unix or windows). If set, skip auto-detection.
|
Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;/// Custom system type (unix or windows). If set, skip auto-detection.
|
||||||
@JsonKey(includeIfNull: false) SystemType? get customSystemType;/// Disabled command types for this server
|
@JsonKey(includeIfNull: false) SystemType? get customSystemType;/// Disabled command types for this server
|
||||||
@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes;
|
@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes;
|
||||||
@@ -33,12 +38,12 @@ $SpiCopyWith<Spi> get copyWith => _$SpiCopyWithImpl<Spi>(this as Spi, _$identity
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other.disabledCmdTypes, disabledCmdTypes));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&const DeepCollectionEquality().equals(other.jumpChainIds, jumpChainIds)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other.disabledCmdTypes, disabledCmdTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType,const DeepCollectionEquality().hash(disabledCmdTypes));
|
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,const DeepCollectionEquality().hash(jumpChainIds),custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType,const DeepCollectionEquality().hash(disabledCmdTypes));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +54,7 @@ abstract mixin class $SpiCopyWith<$Res> {
|
|||||||
factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl;
|
factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId,@JsonKey(includeIfNull: false) List<String>? jumpChainIds, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +71,7 @@ class _$SpiCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of Spi
|
/// Create a copy of Spi
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? jumpChainIds = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -78,7 +83,8 @@ as String?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to
|
|||||||
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
as String?,jumpChainIds: freezed == jumpChainIds ? _self.jumpChainIds : jumpChainIds // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
||||||
as WakeOnLanCfg?,envs: freezed == envs ? _self.envs : envs // ignore: cast_nullable_to_non_nullable
|
as WakeOnLanCfg?,envs: freezed == envs ? _self.envs : envs // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -169,10 +175,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, @JsonKey(includeIfNull: false) List<String>? jumpChainIds, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Spi() when $default != null:
|
case _Spi() when $default != null:
|
||||||
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.jumpChainIds,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -190,10 +196,10 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, @JsonKey(includeIfNull: false) List<String>? jumpChainIds, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Spi():
|
case _Spi():
|
||||||
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.jumpChainIds,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -210,10 +216,10 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, @JsonKey(includeIfNull: false) List<String>? jumpChainIds, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Spi() when $default != null:
|
case _Spi() when $default != null:
|
||||||
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.jumpChainIds,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -225,7 +231,7 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
|
|||||||
|
|
||||||
@JsonSerializable(includeIfNull: false)
|
@JsonSerializable(includeIfNull: false)
|
||||||
class _Spi extends Spi {
|
class _Spi extends Spi {
|
||||||
const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', @JsonKey(includeIfNull: false) this.customSystemType, @JsonKey(includeIfNull: false) final List<String>? disabledCmdTypes}): _tags = tags,_envs = envs,_disabledCmdTypes = disabledCmdTypes,super._();
|
const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, @JsonKey(includeIfNull: false) final List<String>? jumpChainIds, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', @JsonKey(includeIfNull: false) this.customSystemType, @JsonKey(includeIfNull: false) final List<String>? disabledCmdTypes}): _tags = tags,_jumpChainIds = jumpChainIds,_envs = envs,_disabledCmdTypes = disabledCmdTypes,super._();
|
||||||
factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
|
factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
|
||||||
|
|
||||||
@override final String name;
|
@override final String name;
|
||||||
@@ -246,8 +252,25 @@ class _Spi extends Spi {
|
|||||||
|
|
||||||
@override final String? alterUrl;
|
@override final String? alterUrl;
|
||||||
@override@JsonKey() final bool autoConnect;
|
@override@JsonKey() final bool autoConnect;
|
||||||
/// [id] of the jump server
|
/// [id] of the jump server (legacy, single hop)
|
||||||
|
///
|
||||||
|
/// Migrated to [jumpChainIds].
|
||||||
@override final String? jumpId;
|
@override final String? jumpId;
|
||||||
|
/// Jump chain hop ids (nearest -> farthest)
|
||||||
|
///
|
||||||
|
/// Preferred over [jumpId].
|
||||||
|
final List<String>? _jumpChainIds;
|
||||||
|
/// Jump chain hop ids (nearest -> farthest)
|
||||||
|
///
|
||||||
|
/// Preferred over [jumpId].
|
||||||
|
@override@JsonKey(includeIfNull: false) List<String>? get jumpChainIds {
|
||||||
|
final value = _jumpChainIds;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_jumpChainIds is EqualUnmodifiableListView) return _jumpChainIds;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
@override final ServerCustom? custom;
|
@override final ServerCustom? custom;
|
||||||
@override final WakeOnLanCfg? wolCfg;
|
@override final WakeOnLanCfg? wolCfg;
|
||||||
/// It only applies to SSH terminal.
|
/// It only applies to SSH terminal.
|
||||||
@@ -289,12 +312,12 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other._disabledCmdTypes, _disabledCmdTypes));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&const DeepCollectionEquality().equals(other._jumpChainIds, _jumpChainIds)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other._disabledCmdTypes, _disabledCmdTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType,const DeepCollectionEquality().hash(_disabledCmdTypes));
|
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,const DeepCollectionEquality().hash(_jumpChainIds),custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType,const DeepCollectionEquality().hash(_disabledCmdTypes));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -305,7 +328,7 @@ abstract mixin class _$SpiCopyWith<$Res> implements $SpiCopyWith<$Res> {
|
|||||||
factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl;
|
factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId,@JsonKey(includeIfNull: false) List<String>? jumpChainIds, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -322,7 +345,7 @@ class __$SpiCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of Spi
|
/// Create a copy of Spi
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? jumpChainIds = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
||||||
return _then(_Spi(
|
return _then(_Spi(
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -334,7 +357,8 @@ as String?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_t
|
|||||||
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
as List<String>?,alterUrl: freezed == alterUrl ? _self.alterUrl : alterUrl // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
as String?,autoConnect: null == autoConnect ? _self.autoConnect : autoConnect // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
as bool,jumpId: freezed == jumpId ? _self.jumpId : jumpId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
as String?,jumpChainIds: freezed == jumpChainIds ? _self._jumpChainIds : jumpChainIds // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,custom: freezed == custom ? _self.custom : custom // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: cast_nullable_to_non_nullable
|
||||||
as WakeOnLanCfg?,envs: freezed == envs ? _self._envs : envs // ignore: cast_nullable_to_non_nullable
|
as WakeOnLanCfg?,envs: freezed == envs ? _self._envs : envs // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ _Spi _$SpiFromJson(Map<String, dynamic> json) => _Spi(
|
|||||||
alterUrl: json['alterUrl'] as String?,
|
alterUrl: json['alterUrl'] as String?,
|
||||||
autoConnect: json['autoConnect'] as bool? ?? true,
|
autoConnect: json['autoConnect'] as bool? ?? true,
|
||||||
jumpId: json['jumpId'] as String?,
|
jumpId: json['jumpId'] as String?,
|
||||||
|
jumpChainIds: (json['jumpChainIds'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
custom: json['custom'] == null
|
custom: json['custom'] == null
|
||||||
? null
|
? null
|
||||||
: ServerCustom.fromJson(json['custom'] as Map<String, dynamic>),
|
: ServerCustom.fromJson(json['custom'] as Map<String, dynamic>),
|
||||||
@@ -41,19 +44,19 @@ Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
|
|||||||
'ip': instance.ip,
|
'ip': instance.ip,
|
||||||
'port': instance.port,
|
'port': instance.port,
|
||||||
'user': instance.user,
|
'user': instance.user,
|
||||||
if (instance.pwd case final value?) 'pwd': value,
|
'pwd': ?instance.pwd,
|
||||||
if (instance.keyId case final value?) 'pubKeyId': value,
|
'pubKeyId': ?instance.keyId,
|
||||||
if (instance.tags case final value?) 'tags': value,
|
'tags': ?instance.tags,
|
||||||
if (instance.alterUrl case final value?) 'alterUrl': value,
|
'alterUrl': ?instance.alterUrl,
|
||||||
'autoConnect': instance.autoConnect,
|
'autoConnect': instance.autoConnect,
|
||||||
if (instance.jumpId case final value?) 'jumpId': value,
|
'jumpId': ?instance.jumpId,
|
||||||
if (instance.custom case final value?) 'custom': value,
|
'jumpChainIds': ?instance.jumpChainIds,
|
||||||
if (instance.wolCfg case final value?) 'wolCfg': value,
|
'custom': ?instance.custom,
|
||||||
if (instance.envs case final value?) 'envs': value,
|
'wolCfg': ?instance.wolCfg,
|
||||||
|
'envs': ?instance.envs,
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
if (_$SystemTypeEnumMap[instance.customSystemType] case final value?)
|
'customSystemType': ?_$SystemTypeEnumMap[instance.customSystemType],
|
||||||
'customSystemType': value,
|
'disabledCmdTypes': ?instance.disabledCmdTypes,
|
||||||
if (instance.disabledCmdTypes case final value?) 'disabledCmdTypes': value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$SystemTypeEnumMap = {
|
const _$SystemTypeEnumMap = {
|
||||||
|
|||||||
@@ -378,18 +378,27 @@ void _parseWindowsCpuData(ServerStatusUpdateReq req, Map<String, String> parsedO
|
|||||||
// Windows CPU parsing - JSON format from PowerShell
|
// Windows CPU parsing - JSON format from PowerShell
|
||||||
final cpuRaw = WindowsStatusCmdType.cpu.findInMap(parsedOutput);
|
final cpuRaw = WindowsStatusCmdType.cpu.findInMap(parsedOutput);
|
||||||
if (cpuRaw.isNotEmpty && cpuRaw != 'null' && !cpuRaw.contains('error') && !cpuRaw.contains('Exception')) {
|
if (cpuRaw.isNotEmpty && cpuRaw != 'null' && !cpuRaw.contains('error') && !cpuRaw.contains('Exception')) {
|
||||||
final cpus = WindowsParser.parseCpu(cpuRaw, req.ss);
|
final cpuResult = WindowsParser.parseCpu(cpuRaw, req.ss);
|
||||||
if (cpus.isNotEmpty) {
|
if (cpuResult.cores.isNotEmpty) {
|
||||||
req.ss.cpu.update(cpus);
|
req.ss.cpu.update(cpuResult.cores);
|
||||||
|
final brandRaw = WindowsStatusCmdType.cpuBrand.findInMap(parsedOutput);
|
||||||
|
if (brandRaw.isNotEmpty && brandRaw != 'null') {
|
||||||
|
req.ss.cpu.brand.clear();
|
||||||
|
final brandLines = brandRaw.trim().split('\n');
|
||||||
|
final uniqueBrands = <String>{};
|
||||||
|
for (final line in brandLines) {
|
||||||
|
final trimmedLine = line.trim();
|
||||||
|
if (trimmedLine.isNotEmpty) {
|
||||||
|
uniqueBrands.add(trimmedLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniqueBrands.isNotEmpty) {
|
||||||
|
final brandName = uniqueBrands.first;
|
||||||
|
req.ss.cpu.brand[brandName] = cpuResult.coreCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows CPU brand parsing
|
|
||||||
final brandRaw = WindowsStatusCmdType.cpuBrand.findInMap(parsedOutput);
|
|
||||||
if (brandRaw.isNotEmpty && brandRaw != 'null') {
|
|
||||||
req.ss.cpu.brand.clear();
|
|
||||||
req.ss.cpu.brand[brandRaw.trim()] = 1;
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Loggers.app.warning('Windows CPU parsing failed: $e', s);
|
Loggers.app.warning('Windows CPU parsing failed: $e', s);
|
||||||
}
|
}
|
||||||
@@ -427,8 +436,11 @@ void _parseWindowsDiskData(ServerStatusUpdateReq req, Map<String, String> parsed
|
|||||||
/// Parse Windows uptime data
|
/// Parse Windows uptime data
|
||||||
void _parseWindowsUptimeData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
void _parseWindowsUptimeData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||||
try {
|
try {
|
||||||
final uptime = WindowsParser.parseUpTime(WindowsStatusCmdType.uptime.findInMap(parsedOutput));
|
final uptimeRaw = WindowsStatusCmdType.uptime.findInMap(parsedOutput);
|
||||||
if (uptime != null) {
|
if (uptimeRaw.isNotEmpty && uptimeRaw != 'null') {
|
||||||
|
// PowerShell now returns pre-formatted uptime string (e.g., "28 days, 5:00" or "5:00")
|
||||||
|
// No parsing needed - use it directly
|
||||||
|
final uptime = uptimeRaw.trim();
|
||||||
req.ss.more[StatusCmdType.uptime] = uptime;
|
req.ss.more[StatusCmdType.uptime] = uptime;
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -541,38 +553,36 @@ List<NetSpeedPart> _parseWindowsNetwork(String raw, int currentTime) {
|
|||||||
final dynamic jsonData = json.decode(raw);
|
final dynamic jsonData = json.decode(raw);
|
||||||
final List<NetSpeedPart> netParts = [];
|
final List<NetSpeedPart> netParts = [];
|
||||||
|
|
||||||
// PowerShell Get-Counter returns a structure with CounterSamples
|
if (jsonData is List && jsonData.length >= 2) {
|
||||||
if (jsonData is Map && jsonData.containsKey('CounterSamples')) {
|
var sample1 = jsonData[jsonData.length - 2];
|
||||||
final samples = jsonData['CounterSamples'] as List?;
|
var sample2 = jsonData[jsonData.length - 1];
|
||||||
if (samples != null && samples.length >= 2) {
|
if (sample1 is Map && sample1.containsKey('value')) {
|
||||||
// We need 2 samples to calculate speed (interval between them)
|
sample1 = sample1['value'];
|
||||||
final Map<String, double> interfaceRx = {};
|
}
|
||||||
final Map<String, double> interfaceTx = {};
|
if (sample2 is Map && sample2.containsKey('value')) {
|
||||||
|
sample2 = sample2['value'];
|
||||||
for (final sample in samples) {
|
}
|
||||||
final path = sample['Path']?.toString() ?? '';
|
if (sample1 is List && sample2 is List && sample1.length == sample2.length) {
|
||||||
final cookedValue = sample['CookedValue'] as num? ?? 0;
|
for (int i = 0; i < sample1.length; i++) {
|
||||||
|
final s1 = sample1[i];
|
||||||
if (path.contains('Bytes Received/sec')) {
|
final s2 = sample2[i];
|
||||||
final interfaceName = _extractInterfaceName(path);
|
final name = s1['Name']?.toString() ?? '';
|
||||||
if (interfaceName.isNotEmpty) {
|
if (name.isEmpty || name == '_Total') continue;
|
||||||
interfaceRx[interfaceName] = cookedValue.toDouble();
|
final rx1 = (s1['BytesReceivedPersec'] as num?)?.toDouble() ?? 0;
|
||||||
}
|
final rx2 = (s2['BytesReceivedPersec'] as num?)?.toDouble() ?? 0;
|
||||||
} else if (path.contains('Bytes Sent/sec')) {
|
final tx1 = (s1['BytesSentPersec'] as num?)?.toDouble() ?? 0;
|
||||||
final interfaceName = _extractInterfaceName(path);
|
final tx2 = (s2['BytesSentPersec'] as num?)?.toDouble() ?? 0;
|
||||||
if (interfaceName.isNotEmpty) {
|
final time1 = (s1['Timestamp_Sys100NS'] as num?)?.toDouble() ?? 0;
|
||||||
interfaceTx[interfaceName] = cookedValue.toDouble();
|
final time2 = (s2['Timestamp_Sys100NS'] as num?)?.toDouble() ?? 0;
|
||||||
}
|
final timeDelta = (time2 - time1) / 10000000;
|
||||||
}
|
if (timeDelta <= 0) continue;
|
||||||
}
|
final rxDelta = rx2 - rx1;
|
||||||
|
final txDelta = tx2 - tx1;
|
||||||
// Create NetSpeedPart for each interface
|
if (rxDelta < 0 || txDelta < 0) continue;
|
||||||
for (final interfaceName in interfaceRx.keys) {
|
final rxSpeed = rxDelta / timeDelta;
|
||||||
final rx = interfaceRx[interfaceName] ?? 0;
|
final txSpeed = txDelta / timeDelta;
|
||||||
final tx = interfaceTx[interfaceName] ?? 0;
|
|
||||||
|
|
||||||
netParts.add(
|
netParts.add(
|
||||||
NetSpeedPart(interfaceName, BigInt.from(rx.toInt()), BigInt.from(tx.toInt()), currentTime),
|
NetSpeedPart(name, BigInt.from(rxSpeed.toInt()), BigInt.from(txSpeed.toInt()), currentTime),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,53 +594,45 @@ List<NetSpeedPart> _parseWindowsNetwork(String raw, int currentTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _extractInterfaceName(String path) {
|
|
||||||
// Extract interface name from path like
|
|
||||||
// "\\Computer\\NetworkInterface(Interface Name)\\..."
|
|
||||||
final match = RegExp(r'\\NetworkInterface\(([^)]+)\)\\').firstMatch(path);
|
|
||||||
return match?.group(1) ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DiskIOPiece> _parseWindowsDiskIO(String raw, int currentTime) {
|
List<DiskIOPiece> _parseWindowsDiskIO(String raw, int currentTime) {
|
||||||
try {
|
try {
|
||||||
final dynamic jsonData = json.decode(raw);
|
final dynamic jsonData = json.decode(raw);
|
||||||
final List<DiskIOPiece> diskParts = [];
|
final List<DiskIOPiece> diskParts = [];
|
||||||
|
|
||||||
// PowerShell Get-Counter returns a structure with CounterSamples
|
if (jsonData is List && jsonData.length >= 2) {
|
||||||
if (jsonData is Map && jsonData.containsKey('CounterSamples')) {
|
var sample1 = jsonData[jsonData.length - 2];
|
||||||
final samples = jsonData['CounterSamples'] as List?;
|
var sample2 = jsonData[jsonData.length - 1];
|
||||||
if (samples != null) {
|
if (sample1 is Map && sample1.containsKey('value')) {
|
||||||
final Map<String, double> diskReads = {};
|
sample1 = sample1['value'];
|
||||||
final Map<String, double> diskWrites = {};
|
}
|
||||||
|
if (sample2 is Map && sample2.containsKey('value')) {
|
||||||
for (final sample in samples) {
|
sample2 = sample2['value'];
|
||||||
final path = sample['Path']?.toString() ?? '';
|
}
|
||||||
final cookedValue = sample['CookedValue'] as num? ?? 0;
|
if (sample1 is List && sample2 is List && sample1.length == sample2.length) {
|
||||||
|
for (int i = 0; i < sample1.length; i++) {
|
||||||
if (path.contains('Disk Read Bytes/sec')) {
|
final s1 = sample1[i];
|
||||||
final diskName = _extractDiskName(path);
|
final s2 = sample2[i];
|
||||||
if (diskName.isNotEmpty) {
|
final name = s1['Name']?.toString() ?? '';
|
||||||
diskReads[diskName] = cookedValue.toDouble();
|
if (name.isEmpty || name == '_Total') continue;
|
||||||
}
|
final read1 = (s1['DiskReadBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||||
} else if (path.contains('Disk Write Bytes/sec')) {
|
final read2 = (s2['DiskReadBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||||
final diskName = _extractDiskName(path);
|
final write1 = (s1['DiskWriteBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||||
if (diskName.isNotEmpty) {
|
final write2 = (s2['DiskWriteBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||||
diskWrites[diskName] = cookedValue.toDouble();
|
final time1 = (s1['Timestamp_Sys100NS'] as num?)?.toDouble() ?? 0;
|
||||||
}
|
final time2 = (s2['Timestamp_Sys100NS'] as num?)?.toDouble() ?? 0;
|
||||||
}
|
final timeDelta = (time2 - time1) / 10000000;
|
||||||
}
|
if (timeDelta <= 0) continue;
|
||||||
|
final readDelta = read2 - read1;
|
||||||
// Create DiskIOPiece for each disk - convert bytes to sectors
|
final writeDelta = write2 - write1;
|
||||||
// (assuming 512 bytes per sector)
|
if (readDelta < 0 || writeDelta < 0) continue;
|
||||||
for (final diskName in diskReads.keys) {
|
final readSpeed = readDelta / timeDelta;
|
||||||
final readBytes = diskReads[diskName] ?? 0;
|
final writeSpeed = writeDelta / timeDelta;
|
||||||
final writeBytes = diskWrites[diskName] ?? 0;
|
final sectorsRead = (readSpeed / 512).round();
|
||||||
final sectorsRead = (readBytes / 512).round();
|
final sectorsWrite = (writeSpeed / 512).round();
|
||||||
final sectorsWrite = (writeBytes / 512).round();
|
|
||||||
|
|
||||||
diskParts.add(
|
diskParts.add(
|
||||||
DiskIOPiece(
|
DiskIOPiece(
|
||||||
dev: diskName,
|
dev: name,
|
||||||
sectorsRead: sectorsRead,
|
sectorsRead: sectorsRead,
|
||||||
sectorsWrite: sectorsWrite,
|
sectorsWrite: sectorsWrite,
|
||||||
time: currentTime,
|
time: currentTime,
|
||||||
@@ -646,13 +648,6 @@ List<DiskIOPiece> _parseWindowsDiskIO(String raw, int currentTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _extractDiskName(String path) {
|
|
||||||
// Extract disk name from path like
|
|
||||||
// "\\Computer\\PhysicalDisk(Disk Name)\\..."
|
|
||||||
final match = RegExp(r'\\PhysicalDisk\(([^)]+)\)\\').firstMatch(path);
|
|
||||||
return match?.group(1) ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
||||||
try {
|
try {
|
||||||
// Handle error output
|
// Handle error output
|
||||||
@@ -684,7 +679,7 @@ void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
|||||||
if (typeLines.isNotEmpty && valueLines.isNotEmpty) {
|
if (typeLines.isNotEmpty && valueLines.isNotEmpty) {
|
||||||
temps.parse(typeLines.join('\n'), valueLines.join('\n'));
|
temps.parse(typeLines.join('\n'), valueLines.join('\n'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
// If JSON parsing fails, ignore temperature data
|
Loggers.app.warning('Failed to parse Windows temperature data', e, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,27 +37,39 @@ class Fifo<T> extends ListBase<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class TimeSeq<T extends List<TimeSeqIface>> extends Fifo<T> {
|
abstract class TimeSeq<T extends TimeSeqIface<T>> extends Fifo<List<T>> {
|
||||||
/// Due to the design, at least two elements are required, otherwise [pre] /
|
/// Due to the design, at least two elements are required, otherwise [pre] /
|
||||||
/// [now] will throw.
|
/// [now] will throw.
|
||||||
TimeSeq(T init1, T init2, {super.capacity}) : super(list: [init1, init2]);
|
TimeSeq(List<T> init1, List<T> init2, {super.capacity}) : super(list: [init1, init2]);
|
||||||
|
|
||||||
T get pre {
|
List<T> get pre {
|
||||||
return _list[length - 2];
|
return _list[length - 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
T get now {
|
List<T> get now {
|
||||||
return _list[length - 1];
|
return _list[length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate();
|
void onUpdate();
|
||||||
|
|
||||||
void update(T new_) {
|
void update(List<T> new_) {
|
||||||
add(new_);
|
add(new_);
|
||||||
|
|
||||||
if (pre.length != now.length) {
|
if (pre.length != now.length) {
|
||||||
pre.removeWhere((e) => now.any((el) => e.same(el)));
|
final previous = pre.toList(growable: false);
|
||||||
pre.addAll(now.where((e) => pre.every((el) => !e.same(el))));
|
final remaining = previous.toList(growable: true);
|
||||||
|
final aligned = <T>[];
|
||||||
|
|
||||||
|
for (final current in now) {
|
||||||
|
final matchIndex = remaining.indexWhere((item) => item.same(current));
|
||||||
|
if (matchIndex >= 0) {
|
||||||
|
aligned.add(remaining.removeAt(matchIndex));
|
||||||
|
} else {
|
||||||
|
aligned.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_list[length - 2] = aligned;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ import 'package:server_box/data/model/server/disk.dart';
|
|||||||
import 'package:server_box/data/model/server/memory.dart';
|
import 'package:server_box/data/model/server/memory.dart';
|
||||||
import 'package:server_box/data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
|
|
||||||
|
/// Windows CPU parse result
|
||||||
|
class WindowsCpuResult {
|
||||||
|
final List<SingleCpuCore> cores;
|
||||||
|
final int coreCount;
|
||||||
|
const WindowsCpuResult(this.cores, this.coreCount);
|
||||||
|
}
|
||||||
|
|
||||||
/// Windows-specific status parsing utilities
|
/// Windows-specific status parsing utilities
|
||||||
///
|
///
|
||||||
/// This module handles parsing of Windows PowerShell command outputs
|
/// This module handles parsing of Windows PowerShell command outputs
|
||||||
@@ -94,30 +101,75 @@ class WindowsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse Windows CPU information from PowerShell output
|
/// Parse Windows CPU information from PowerShell output
|
||||||
static List<SingleCpuCore> parseCpu(String raw, ServerStatus serverStatus) {
|
/// Returns WindowsCpuResult containing CPU cores and total core count
|
||||||
|
static WindowsCpuResult parseCpu(String raw, ServerStatus serverStatus) {
|
||||||
try {
|
try {
|
||||||
final dynamic jsonData = json.decode(raw);
|
final dynamic jsonData = json.decode(raw);
|
||||||
final List<SingleCpuCore> cpus = [];
|
final List<SingleCpuCore> cpus = [];
|
||||||
|
int totalCoreCount = 1;
|
||||||
|
|
||||||
if (jsonData is List) {
|
if (jsonData is List) {
|
||||||
for (int i = 0; i < jsonData.length; i++) {
|
// Multiple physical processors
|
||||||
final cpu = jsonData[i];
|
totalCoreCount = 0; // Reset to sum up
|
||||||
final loadPercentage = cpu['LoadPercentage'] ?? 0;
|
var logicalProcessorOffset = 0;
|
||||||
final usage = loadPercentage as int;
|
final prevCpus = serverStatus.cpu.now;
|
||||||
|
for (int procIdx = 0; procIdx < jsonData.length; procIdx++) {
|
||||||
|
final processor = jsonData[procIdx];
|
||||||
|
final loadPercentage = (processor['LoadPercentage'] as num?) ?? 0;
|
||||||
|
final numberOfCores = (processor['NumberOfCores'] as int?) ?? 1;
|
||||||
|
final numberOfLogicalProcessors = (processor['NumberOfLogicalProcessors'] as int?) ?? numberOfCores;
|
||||||
|
totalCoreCount += numberOfCores;
|
||||||
|
final usage = loadPercentage.toInt();
|
||||||
final idle = 100 - usage;
|
final idle = 100 - usage;
|
||||||
|
|
||||||
// Get previous CPU data to calculate cumulative values
|
// Create a SingleCpuCore entry for each logical processor
|
||||||
final prevCpus = serverStatus.cpu.now;
|
// Windows only reports overall CPU load, so we distribute it evenly
|
||||||
final prevCpu = i < prevCpus.length ? prevCpus[i] : null;
|
for (int i = 0; i < numberOfLogicalProcessors; i++) {
|
||||||
|
final coreId = logicalProcessorOffset + i;
|
||||||
|
// Skip summary entry at index 0 when looking up previous samples
|
||||||
|
final prevIndex = coreId + 1;
|
||||||
|
final prevCpu = prevIndex < prevCpus.length ? prevCpus[prevIndex] : null;
|
||||||
|
|
||||||
// LIMITATION: Windows CPU counters approach
|
// LIMITATION: Windows CPU counters approach
|
||||||
// PowerShell provides LoadPercentage as instantaneous percentage, not cumulative time.
|
// PowerShell provides LoadPercentage as instantaneous percentage, not cumulative time.
|
||||||
// We simulate cumulative counters by adding current percentages to previous totals.
|
// We simulate cumulative counters by adding current percentages to previous totals.
|
||||||
// This approach has limitations:
|
// Additionally, Windows only provides overall CPU load, not per-core load.
|
||||||
// 1. Not as accurate as true cumulative time counters (Linux /proc/stat)
|
// We distribute the load evenly across all logical processors.
|
||||||
// 2. May drift over time with variable polling intervals
|
final newUser = (prevCpu?.user ?? 0) + usage;
|
||||||
// 3. Results depend on consistent polling frequency
|
final newIdle = (prevCpu?.idle ?? 0) + idle;
|
||||||
// However, this allows compatibility with existing delta-based CPU calculation logic.
|
|
||||||
|
cpus.add(
|
||||||
|
SingleCpuCore(
|
||||||
|
'cpu$coreId',
|
||||||
|
newUser, // cumulative user time
|
||||||
|
0, // sys (not available)
|
||||||
|
0, // nice (not available)
|
||||||
|
newIdle, // cumulative idle time
|
||||||
|
0, // iowait (not available)
|
||||||
|
0, // irq (not available)
|
||||||
|
0, // softirq (not available)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
logicalProcessorOffset += numberOfLogicalProcessors;
|
||||||
|
}
|
||||||
|
} else if (jsonData is Map) {
|
||||||
|
// Single physical processor
|
||||||
|
final loadPercentage = (jsonData['LoadPercentage'] as num?) ?? 0;
|
||||||
|
final numberOfCores = (jsonData['NumberOfCores'] as int?) ?? 1;
|
||||||
|
final numberOfLogicalProcessors = (jsonData['NumberOfLogicalProcessors'] as int?) ?? numberOfCores;
|
||||||
|
totalCoreCount = numberOfCores;
|
||||||
|
final usage = loadPercentage.toInt();
|
||||||
|
final idle = 100 - usage;
|
||||||
|
|
||||||
|
// Create a SingleCpuCore entry for each logical processor
|
||||||
|
final prevCpus = serverStatus.cpu.now;
|
||||||
|
for (int i = 0; i < numberOfLogicalProcessors; i++) {
|
||||||
|
// Skip summary entry at index 0 when looking up previous samples
|
||||||
|
final prevIndex = i + 1;
|
||||||
|
final prevCpu = prevIndex < prevCpus.length ? prevCpus[prevIndex] : null;
|
||||||
|
|
||||||
|
// LIMITATION: See comment above for Windows CPU counter limitations
|
||||||
final newUser = (prevCpu?.user ?? 0) + usage;
|
final newUser = (prevCpu?.user ?? 0) + usage;
|
||||||
final newIdle = (prevCpu?.idle ?? 0) + idle;
|
final newIdle = (prevCpu?.idle ?? 0) + idle;
|
||||||
|
|
||||||
@@ -125,46 +177,43 @@ class WindowsParser {
|
|||||||
SingleCpuCore(
|
SingleCpuCore(
|
||||||
'cpu$i',
|
'cpu$i',
|
||||||
newUser, // cumulative user time
|
newUser, // cumulative user time
|
||||||
0, // sys (not available)
|
0, // sys
|
||||||
0, // nice (not available)
|
0, // nice
|
||||||
newIdle, // cumulative idle time
|
newIdle, // cumulative idle time
|
||||||
0, // iowait (not available)
|
0, // iowait
|
||||||
0, // irq (not available)
|
0, // irq
|
||||||
0, // softirq (not available)
|
0, // softirq
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (jsonData is Map) {
|
|
||||||
// Single CPU core
|
|
||||||
final loadPercentage = jsonData['LoadPercentage'] ?? 0;
|
|
||||||
final usage = loadPercentage as int;
|
|
||||||
final idle = 100 - usage;
|
|
||||||
|
|
||||||
// Get previous CPU data to calculate cumulative values
|
|
||||||
final prevCpus = serverStatus.cpu.now;
|
|
||||||
final prevCpu = prevCpus.isNotEmpty ? prevCpus[0] : null;
|
|
||||||
|
|
||||||
// LIMITATION: See comment above for Windows CPU counter limitations
|
|
||||||
final newUser = (prevCpu?.user ?? 0) + usage;
|
|
||||||
final newIdle = (prevCpu?.idle ?? 0) + idle;
|
|
||||||
|
|
||||||
cpus.add(
|
|
||||||
SingleCpuCore(
|
|
||||||
'cpu0',
|
|
||||||
newUser, // cumulative user time
|
|
||||||
0, // sys
|
|
||||||
0, // nice
|
|
||||||
newIdle, // cumulative idle time
|
|
||||||
0, // iowait
|
|
||||||
0, // irq
|
|
||||||
0, // softirq
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cpus;
|
// Add a summary entry at the beginning (like Linux 'cpu' line)
|
||||||
} catch (e) {
|
// This is the aggregate of all logical processors
|
||||||
return [];
|
if (cpus.isNotEmpty) {
|
||||||
|
int totalUser = 0;
|
||||||
|
int totalIdle = 0;
|
||||||
|
for (final core in cpus) {
|
||||||
|
totalUser += core.user;
|
||||||
|
totalIdle += core.idle;
|
||||||
|
}
|
||||||
|
// Insert at the beginning with ID 'cpu' (matching Linux format)
|
||||||
|
cpus.insert(0, SingleCpuCore(
|
||||||
|
'cpu', // Summary entry, like Linux
|
||||||
|
totalUser,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
totalIdle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return WindowsCpuResult(cpus, totalCoreCount);
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Windows CPU parsing failed: $e', s);
|
||||||
|
return WindowsCpuResult([], 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ Map<String, dynamic> _$WakeOnLanCfgToJson(WakeOnLanCfg instance) =>
|
|||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'mac': instance.mac,
|
'mac': instance.mac,
|
||||||
'ip': instance.ip,
|
'ip': instance.ip,
|
||||||
if (instance.pwd case final value?) 'pwd': value,
|
'pwd': ?instance.pwd,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,17 +6,32 @@ class SftpReq {
|
|||||||
final String localPath;
|
final String localPath;
|
||||||
final SftpReqType type;
|
final SftpReqType type;
|
||||||
String? privateKey;
|
String? privateKey;
|
||||||
Spi? jumpSpi;
|
List<Spi>? jumpChain;
|
||||||
String? jumpPrivateKey;
|
List<String?>? jumpPrivateKeys;
|
||||||
|
Map<String, String>? knownHostFingerprints;
|
||||||
|
|
||||||
SftpReq(this.spi, this.remotePath, this.localPath, this.type) {
|
SftpReq(this.spi, this.remotePath, this.localPath, this.type) {
|
||||||
final keyId = spi.keyId;
|
final keyId = spi.keyId;
|
||||||
if (keyId != null) {
|
if (keyId != null) {
|
||||||
privateKey = getPrivateKey(keyId);
|
privateKey = getPrivateKey(keyId);
|
||||||
}
|
}
|
||||||
if (spi.jumpId != null) {
|
if (spi.jumpChainIds != null || spi.jumpId != null) {
|
||||||
jumpSpi = Stores.server.box.get(spi.jumpId);
|
// Use resolveMergedJumpChain to recursively expand nested hop chains
|
||||||
jumpPrivateKey = Stores.key.fetchOne(jumpSpi?.keyId)?.key;
|
final chain = resolveMergedJumpChain(spi);
|
||||||
|
final keys = <String?>[];
|
||||||
|
for (final hop in chain) {
|
||||||
|
keys.add(hop.keyId != null ? getPrivateKey(hop.keyId!) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always set when a jump is configured so the isolate won't fallback to Stores.
|
||||||
|
jumpChain = chain;
|
||||||
|
jumpPrivateKeys = keys;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
knownHostFingerprints = Map<String, String>.from(Stores.setting.sshKnownHostFingerprints.get());
|
||||||
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to load SSH known host fingerprints', e, s);
|
||||||
|
knownHostFingerprints = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +45,7 @@ class SftpReqStatus {
|
|||||||
late SftpWorker worker;
|
late SftpWorker worker;
|
||||||
final Completer? completer;
|
final Completer? completer;
|
||||||
|
|
||||||
String get fileName => req.localPath.split('/').last;
|
String get fileName => req.localPath.split(Pfs.seperator).last;
|
||||||
|
|
||||||
// status of the download
|
// status of the download
|
||||||
double? progress;
|
double? progress;
|
||||||
@@ -83,4 +98,4 @@ class SftpReqStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SftpWorkerStatus { preparing, sshConnectted, loading, finished }
|
enum SftpWorkerStatus { preparing, sshConnected, loading, finished }
|
||||||
|
|||||||
@@ -63,13 +63,14 @@ Future<void> _download(SftpReq req, SendPort mainSendPort, SendErrorFunction sen
|
|||||||
final client = await genClient(
|
final client = await genClient(
|
||||||
req.spi,
|
req.spi,
|
||||||
privateKey: req.privateKey,
|
privateKey: req.privateKey,
|
||||||
jumpSpi: req.jumpSpi,
|
jumpChain: req.jumpChain,
|
||||||
jumpPrivateKey: req.jumpPrivateKey,
|
jumpPrivateKeys: req.jumpPrivateKeys,
|
||||||
|
knownHostFingerprints: req.knownHostFingerprints,
|
||||||
);
|
);
|
||||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
mainSendPort.send(SftpWorkerStatus.sshConnected);
|
||||||
|
|
||||||
/// Create the directory if not exists
|
/// Create the directory if not exists
|
||||||
final dirPath = req.localPath.substring(0, req.localPath.lastIndexOf('/'));
|
final dirPath = req.localPath.substring(0, req.localPath.lastIndexOf(Pfs.seperator));
|
||||||
await Directory(dirPath).create(recursive: true);
|
await Directory(dirPath).create(recursive: true);
|
||||||
|
|
||||||
/// Use [FileMode.write] to overwrite the file
|
/// Use [FileMode.write] to overwrite the file
|
||||||
@@ -119,10 +120,11 @@ Future<void> _upload(SftpReq req, SendPort mainSendPort, SendErrorFunction sendE
|
|||||||
final client = await genClient(
|
final client = await genClient(
|
||||||
req.spi,
|
req.spi,
|
||||||
privateKey: req.privateKey,
|
privateKey: req.privateKey,
|
||||||
jumpSpi: req.jumpSpi,
|
jumpChain: req.jumpChain,
|
||||||
jumpPrivateKey: req.jumpPrivateKey,
|
jumpPrivateKeys: req.jumpPrivateKeys,
|
||||||
|
knownHostFingerprints: req.knownHostFingerprints,
|
||||||
);
|
);
|
||||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
mainSendPort.send(SftpWorkerStatus.sshConnected);
|
||||||
|
|
||||||
final local = File(req.localPath);
|
final local = File(req.localPath);
|
||||||
if (!await local.exists()) {
|
if (!await local.exists()) {
|
||||||
|
|||||||
343
lib/data/provider/ai/ask_ai.dart
Normal file
343
lib/data/provider/ai/ask_ai.dart
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:riverpod/riverpod.dart';
|
||||||
|
import 'package:server_box/data/model/ai/ask_ai_models.dart';
|
||||||
|
import 'package:server_box/data/res/store.dart';
|
||||||
|
import 'package:server_box/data/store/setting.dart';
|
||||||
|
|
||||||
|
final askAiRepositoryProvider = Provider<AskAiRepository>((ref) {
|
||||||
|
return AskAiRepository();
|
||||||
|
});
|
||||||
|
|
||||||
|
class AskAiRepository {
|
||||||
|
AskAiRepository({Dio? dio}) : _dio = dio ?? Dio();
|
||||||
|
|
||||||
|
final Dio _dio;
|
||||||
|
|
||||||
|
SettingStore get _settings => Stores.setting;
|
||||||
|
|
||||||
|
/// Streams the AI response using the configured endpoint.
|
||||||
|
Stream<AskAiEvent> ask({
|
||||||
|
required String selection,
|
||||||
|
String? localeHint,
|
||||||
|
List<AskAiMessage> conversation = const [],
|
||||||
|
}) async* {
|
||||||
|
final baseUrl = _settings.askAiBaseUrl.fetch().trim();
|
||||||
|
final apiKey = _settings.askAiApiKey.fetch().trim();
|
||||||
|
final model = _settings.askAiModel.fetch().trim();
|
||||||
|
|
||||||
|
final missing = <AskAiConfigField>[];
|
||||||
|
if (baseUrl.isEmpty) missing.add(AskAiConfigField.baseUrl);
|
||||||
|
if (apiKey.isEmpty) missing.add(AskAiConfigField.apiKey);
|
||||||
|
if (model.isEmpty) missing.add(AskAiConfigField.model);
|
||||||
|
if (missing.isNotEmpty) {
|
||||||
|
throw AskAiConfigException(missingFields: missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
final parsedBaseUri = Uri.tryParse(baseUrl);
|
||||||
|
final hasScheme = parsedBaseUri?.hasScheme ?? false;
|
||||||
|
final hasHost = (parsedBaseUri?.host ?? '').isNotEmpty;
|
||||||
|
if (!hasScheme || !hasHost) {
|
||||||
|
throw AskAiConfigException(invalidBaseUrl: baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = _composeUri(baseUrl, '/v1/chat/completions');
|
||||||
|
final authHeader = apiKey.startsWith('Bearer ') ? apiKey : 'Bearer $apiKey';
|
||||||
|
final headers = <String, String>{
|
||||||
|
Headers.acceptHeader: 'text/event-stream',
|
||||||
|
Headers.contentTypeHeader: Headers.jsonContentType,
|
||||||
|
'Authorization': authHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
final requestBody = _buildRequestBody(
|
||||||
|
model: model,
|
||||||
|
selection: selection,
|
||||||
|
localeHint: localeHint,
|
||||||
|
conversation: conversation,
|
||||||
|
);
|
||||||
|
|
||||||
|
Response<ResponseBody> response;
|
||||||
|
try {
|
||||||
|
response = await _dio.postUri<ResponseBody>(
|
||||||
|
uri,
|
||||||
|
data: jsonEncode(requestBody),
|
||||||
|
options: Options(
|
||||||
|
responseType: ResponseType.stream,
|
||||||
|
headers: headers,
|
||||||
|
sendTimeout: const Duration(seconds: 20),
|
||||||
|
receiveTimeout: const Duration(minutes: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
throw AskAiNetworkException(message: e.message ?? 'Request failed', cause: e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final body = response.data;
|
||||||
|
if (body == null) {
|
||||||
|
throw AskAiNetworkException(message: 'Empty response body');
|
||||||
|
}
|
||||||
|
|
||||||
|
final contentBuffer = StringBuffer();
|
||||||
|
final commands = <AskAiCommand>[];
|
||||||
|
final toolBuilders = <int, _ToolCallBuilder>{};
|
||||||
|
final utf8Stream = body.stream.cast<List<int>>().transform(utf8.decoder);
|
||||||
|
final carry = StringBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await for (final chunk in utf8Stream) {
|
||||||
|
carry.write(chunk);
|
||||||
|
final segments = carry.toString().split('\n\n');
|
||||||
|
carry
|
||||||
|
..clear()
|
||||||
|
..write(segments.removeLast());
|
||||||
|
|
||||||
|
for (final segment in segments) {
|
||||||
|
final lines = segment.split('\n');
|
||||||
|
for (final rawLine in lines) {
|
||||||
|
final line = rawLine.trim();
|
||||||
|
if (line.isEmpty || !line.startsWith('data:')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final payload = line.substring(5).trim();
|
||||||
|
if (payload.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (payload == '[DONE]') {
|
||||||
|
yield AskAiCompleted(
|
||||||
|
fullText: contentBuffer.toString(),
|
||||||
|
commands: List.unmodifiable(commands),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> json;
|
||||||
|
try {
|
||||||
|
json = jsonDecode(payload) as Map<String, dynamic>;
|
||||||
|
} catch (e, s) {
|
||||||
|
yield AskAiStreamError(e, s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final choices = json['choices'];
|
||||||
|
if (choices is! List || choices.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final choice in choices) {
|
||||||
|
if (choice is! Map<String, dynamic>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final delta = choice['delta'];
|
||||||
|
if (delta is Map<String, dynamic>) {
|
||||||
|
final content = delta['content'];
|
||||||
|
if (content is String && content.isNotEmpty) {
|
||||||
|
contentBuffer.write(content);
|
||||||
|
yield AskAiContentDelta(content);
|
||||||
|
} else if (content is List) {
|
||||||
|
for (final item in content) {
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
final text = item['text'] as String?;
|
||||||
|
if (text != null && text.isNotEmpty) {
|
||||||
|
contentBuffer.write(text);
|
||||||
|
yield AskAiContentDelta(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final toolCalls = delta['tool_calls'];
|
||||||
|
if (toolCalls is List) {
|
||||||
|
for (final toolCall in toolCalls) {
|
||||||
|
if (toolCall is! Map<String, dynamic>) continue;
|
||||||
|
final index = toolCall['index'] as int? ?? 0;
|
||||||
|
final builder = toolBuilders.putIfAbsent(index, _ToolCallBuilder.new);
|
||||||
|
final function = toolCall['function'];
|
||||||
|
if (function is Map<String, dynamic>) {
|
||||||
|
builder.name ??= function['name'] as String?;
|
||||||
|
final args = function['arguments'] as String?;
|
||||||
|
if (args != null && args.isNotEmpty) {
|
||||||
|
builder.arguments.write(args);
|
||||||
|
final command = builder.tryBuild();
|
||||||
|
if (command != null) {
|
||||||
|
commands.add(command);
|
||||||
|
yield AskAiToolSuggestion(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final finishReason = choice['finish_reason'];
|
||||||
|
if (finishReason == 'tool_calls') {
|
||||||
|
for (final builder in toolBuilders.values) {
|
||||||
|
final command = builder.tryBuild(force: true);
|
||||||
|
if (command != null) {
|
||||||
|
commands.add(command);
|
||||||
|
yield AskAiToolSuggestion(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toolBuilders.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush remaining buffer if [DONE] not received.
|
||||||
|
if (contentBuffer.isNotEmpty || commands.isNotEmpty) {
|
||||||
|
yield AskAiCompleted(
|
||||||
|
fullText: contentBuffer.toString(),
|
||||||
|
commands: List.unmodifiable(commands),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
yield AskAiStreamError(e, s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildRequestBody({
|
||||||
|
required String model,
|
||||||
|
required String selection,
|
||||||
|
required List<AskAiMessage> conversation,
|
||||||
|
String? localeHint,
|
||||||
|
}) {
|
||||||
|
final promptBuffer = StringBuffer()
|
||||||
|
..writeln('你是一个 SSH 终端助手。')
|
||||||
|
..writeln('用户会提供一段终端输出或命令,请结合上下文给出解释。')
|
||||||
|
..writeln('当需要给出可执行命令时,调用 `recommend_shell` 工具,并提供简短描述。')
|
||||||
|
..writeln('仅在非常确定命令安全时才给出建议。');
|
||||||
|
|
||||||
|
if (localeHint != null && localeHint.isNotEmpty) {
|
||||||
|
promptBuffer
|
||||||
|
.writeln('请优先使用用户的语言输出:$localeHint。');
|
||||||
|
}
|
||||||
|
|
||||||
|
final messages = <Map<String, String>>[
|
||||||
|
{
|
||||||
|
'role': 'system',
|
||||||
|
'content': promptBuffer.toString(),
|
||||||
|
},
|
||||||
|
...conversation.map((message) => {
|
||||||
|
'role': message.apiRole,
|
||||||
|
'content': message.content,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
'role': 'user',
|
||||||
|
'content': '以下是终端选中的内容:\n$selection',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
'model': model,
|
||||||
|
'stream': true,
|
||||||
|
'messages': messages,
|
||||||
|
'tools': [
|
||||||
|
{
|
||||||
|
'type': 'function',
|
||||||
|
'function': {
|
||||||
|
'name': 'recommend_shell',
|
||||||
|
'description': '返回一个用户可以直接复制执行的终端命令。',
|
||||||
|
'parameters': {
|
||||||
|
'type': 'object',
|
||||||
|
'required': ['command'],
|
||||||
|
'properties': {
|
||||||
|
'command': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': '完整的终端命令,确保可以被粘贴后直接执行。',
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': '简述该命令的作用或注意事项。',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri _composeUri(String base, String path) {
|
||||||
|
final sanitizedBase = base.replaceAll(RegExp(r'/+$'), '');
|
||||||
|
final sanitizedPath = path.replaceFirst(RegExp(r'^/+'), '');
|
||||||
|
return Uri.parse('$sanitizedBase/$sanitizedPath');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ToolCallBuilder {
|
||||||
|
_ToolCallBuilder();
|
||||||
|
|
||||||
|
final StringBuffer arguments = StringBuffer();
|
||||||
|
String? name;
|
||||||
|
bool _emitted = false;
|
||||||
|
|
||||||
|
AskAiCommand? tryBuild({bool force = false}) {
|
||||||
|
if (_emitted && !force) return null;
|
||||||
|
final raw = arguments.toString();
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw) as Map<String, dynamic>;
|
||||||
|
final command = decoded['command'] as String?;
|
||||||
|
if (command == null || command.trim().isEmpty) {
|
||||||
|
if (force) {
|
||||||
|
_emitted = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final description = decoded['description'] as String? ?? decoded['explanation'] as String? ?? '';
|
||||||
|
_emitted = true;
|
||||||
|
return AskAiCommand(
|
||||||
|
command: command.trim(),
|
||||||
|
description: description.trim(),
|
||||||
|
toolName: name,
|
||||||
|
);
|
||||||
|
} on FormatException {
|
||||||
|
if (force) {
|
||||||
|
_emitted = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
enum AskAiConfigField { baseUrl, apiKey, model }
|
||||||
|
|
||||||
|
class AskAiConfigException implements Exception {
|
||||||
|
const AskAiConfigException({this.missingFields = const [], this.invalidBaseUrl});
|
||||||
|
|
||||||
|
final List<AskAiConfigField> missingFields;
|
||||||
|
final String? invalidBaseUrl;
|
||||||
|
|
||||||
|
bool get hasInvalidBaseUrl => (invalidBaseUrl ?? '').isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final parts = <String>[];
|
||||||
|
if (missingFields.isNotEmpty) {
|
||||||
|
parts.add('missing: ${missingFields.map((e) => e.name).join(', ')}');
|
||||||
|
}
|
||||||
|
if (hasInvalidBaseUrl) {
|
||||||
|
parts.add('invalidBaseUrl: $invalidBaseUrl');
|
||||||
|
}
|
||||||
|
if (parts.isEmpty) {
|
||||||
|
return 'AskAiConfigException()';
|
||||||
|
}
|
||||||
|
return 'AskAiConfigException(${parts.join('; ')})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class AskAiNetworkException implements Exception {
|
||||||
|
const AskAiNetworkException({required this.message, this.cause});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
final Object? cause;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AskAiNetworkException(message: $message)';
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'app.g.dart';
|
|
||||||
part 'app.freezed.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
abstract class AppState with _$AppState {
|
|
||||||
const factory AppState() = _AppState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
class AppStates extends _$AppStates {
|
|
||||||
static BuildContext? ctx;
|
|
||||||
|
|
||||||
@override
|
|
||||||
AppState build() {
|
|
||||||
return const AppState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// coverage:ignore-file
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'app.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$AppState {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => runtimeType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AppState()';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class $AppStateCopyWith<$Res> {
|
|
||||||
$AppStateCopyWith(AppState _, $Res Function(AppState) __);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [AppState].
|
|
||||||
extension AppStatePatterns on AppState {
|
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AppState value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// Callbacks receives the raw object, upcasted.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case final Subclass2 value:
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AppState value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState():
|
|
||||||
return $default(_that);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AppState value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to an `orElse` callback.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function()? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState() when $default != null:
|
|
||||||
return $default();case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// As opposed to `map`, this offers destructuring.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case Subclass2(:final field2):
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function() $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState():
|
|
||||||
return $default();case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to returning `null`
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function()? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _AppState() when $default != null:
|
|
||||||
return $default();case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
|
|
||||||
class _AppState implements AppState {
|
|
||||||
const _AppState();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => runtimeType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'AppState()';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'app.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$appStatesHash() => r'ef96f10f6fff0f3dd6d3128ebf070ad79cbc8bc9';
|
|
||||||
|
|
||||||
/// See also [AppStates].
|
|
||||||
@ProviderFor(AppStates)
|
|
||||||
final appStatesProvider = NotifierProvider<AppStates, AppState>.internal(
|
|
||||||
AppStates.new,
|
|
||||||
name: r'appStatesProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$appStatesHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$AppStates = Notifier<AppState>;
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
|
||||||
@@ -38,29 +38,17 @@ class ContainerNotifier extends _$ContainerNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ContainerState build(SSHClient? client, String userName, String hostId, BuildContext context) {
|
ContainerState build(SSHClient? client, String userName, String hostId, BuildContext context) {
|
||||||
this.client = client;
|
|
||||||
this.userName = userName;
|
|
||||||
this.hostId = hostId;
|
|
||||||
this.context = context;
|
|
||||||
|
|
||||||
final type = Stores.container.getType(hostId);
|
final type = Stores.container.getType(hostId);
|
||||||
final initialState = ContainerState(type: type);
|
final initialState = ContainerState(type: type);
|
||||||
|
|
||||||
// Async initialization
|
// Async initialization
|
||||||
Future.microtask(() => refresh());
|
Future.microtask(() => refresh());
|
||||||
|
|
||||||
return initialState;
|
return initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setType(ContainerType type) async {
|
Future<void> setType(ContainerType type) async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(type: type, error: null, runLog: null, items: null, images: null, version: null);
|
||||||
type: type,
|
|
||||||
error: null,
|
|
||||||
runLog: null,
|
|
||||||
items: null,
|
|
||||||
images: null,
|
|
||||||
version: null,
|
|
||||||
);
|
|
||||||
Stores.container.setType(type, hostId);
|
Stores.container.setType(type, hostId);
|
||||||
sudoCompleter = Completer<bool>();
|
sudoCompleter = Completer<bool>();
|
||||||
await refresh();
|
await refresh();
|
||||||
@@ -185,16 +173,20 @@ class ContainerNotifier extends _$ContainerNotifier {
|
|||||||
try {
|
try {
|
||||||
final statsLines = statsRaw.split('\n');
|
final statsLines = statsRaw.split('\n');
|
||||||
statsLines.removeWhere((element) => element.isEmpty);
|
statsLines.removeWhere((element) => element.isEmpty);
|
||||||
for (var item in state.items!) {
|
final items = state.items;
|
||||||
|
if (items == null) return;
|
||||||
|
|
||||||
|
for (var item in items) {
|
||||||
final id = item.id;
|
final id = item.id;
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
|
if (id.length < 5) continue;
|
||||||
final statsLine = statsLines.firstWhereOrNull(
|
final statsLine = statsLines.firstWhereOrNull(
|
||||||
/// Use 5 characters to match the container id, possibility of mismatch
|
/// Use 5 characters to match the container id, possibility of mismatch
|
||||||
/// is very low.
|
/// is very low.
|
||||||
(element) => element.contains(id.substring(0, 5)),
|
(element) => element.contains(id.substring(0, 5)),
|
||||||
);
|
);
|
||||||
if (statsLine == null) continue;
|
if (statsLine == null) continue;
|
||||||
item.parseStats(statsLine);
|
item.parseStats(statsLine, state.version);
|
||||||
}
|
}
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@@ -272,7 +264,6 @@ class ContainerNotifier extends _$ContainerNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const _jsonFmt = '--format "{{json .}}"';
|
const _jsonFmt = '--format "{{json .}}"';
|
||||||
|
|
||||||
enum ContainerCmdType {
|
enum ContainerCmdType {
|
||||||
@@ -289,7 +280,7 @@ enum ContainerCmdType {
|
|||||||
return switch (this) {
|
return switch (this) {
|
||||||
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
||||||
ContainerCmdType.ps => switch (type) {
|
ContainerCmdType.ps => switch (type) {
|
||||||
/// TODO: Rollback to json format when permformance recovers.
|
/// TODO: Rollback to json format when performance recovers.
|
||||||
/// Use [_jsonFmt] in Docker will cause the operation to slow down.
|
/// Use [_jsonFmt] in Docker will cause the operation to slow down.
|
||||||
ContainerType.docker =>
|
ContainerType.docker =>
|
||||||
'$prefix ps -a --format "table {{printf \\"'
|
'$prefix ps -a --format "table {{printf \\"'
|
||||||
|
|||||||
@@ -6,35 +6,98 @@ part of 'container.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$containerNotifierHash() => r'db8f8a6b6071b7b33fbf79128dfed408a5b9fdad';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
@ProviderFor(ContainerNotifier)
|
||||||
class _SystemHash {
|
const containerProvider = ContainerNotifierFamily._();
|
||||||
_SystemHash._();
|
|
||||||
|
|
||||||
static int combine(int hash, int value) {
|
final class ContainerNotifierProvider
|
||||||
// ignore: parameter_assignments
|
extends $NotifierProvider<ContainerNotifier, ContainerState> {
|
||||||
hash = 0x1fffffff & (hash + value);
|
const ContainerNotifierProvider._({
|
||||||
// ignore: parameter_assignments
|
required ContainerNotifierFamily super.from,
|
||||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
required (SSHClient?, String, String, BuildContext) super.argument,
|
||||||
return hash ^ (hash >> 6);
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'containerProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$containerNotifierHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'containerProvider'
|
||||||
|
''
|
||||||
|
'$argument';
|
||||||
}
|
}
|
||||||
|
|
||||||
static int finish(int hash) {
|
@$internal
|
||||||
// ignore: parameter_assignments
|
@override
|
||||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
ContainerNotifier create() => ContainerNotifier();
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = hash ^ (hash >> 11);
|
/// {@macro riverpod.override_with_value}
|
||||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
Override overrideWithValue(ContainerState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ContainerState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ContainerNotifierProvider && other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _$ContainerNotifier
|
String _$containerNotifierHash() => r'85457ec75264199c284572ee45beeaccba2044a1';
|
||||||
extends BuildlessAutoDisposeNotifier<ContainerState> {
|
|
||||||
late final SSHClient? client;
|
final class ContainerNotifierFamily extends $Family
|
||||||
late final String userName;
|
with
|
||||||
late final String hostId;
|
$ClassFamilyOverride<
|
||||||
late final BuildContext context;
|
ContainerNotifier,
|
||||||
|
ContainerState,
|
||||||
|
ContainerState,
|
||||||
|
ContainerState,
|
||||||
|
(SSHClient?, String, String, BuildContext)
|
||||||
|
> {
|
||||||
|
const ContainerNotifierFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'containerProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
ContainerNotifierProvider call(
|
||||||
|
SSHClient? client,
|
||||||
|
String userName,
|
||||||
|
String hostId,
|
||||||
|
BuildContext context,
|
||||||
|
) => ContainerNotifierProvider._(
|
||||||
|
argument: (client, userName, hostId, context),
|
||||||
|
from: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'containerProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$ContainerNotifier extends $Notifier<ContainerState> {
|
||||||
|
late final _$args = ref.$arg as (SSHClient?, String, String, BuildContext);
|
||||||
|
SSHClient? get client => _$args.$1;
|
||||||
|
String get userName => _$args.$2;
|
||||||
|
String get hostId => _$args.$3;
|
||||||
|
BuildContext get context => _$args.$4;
|
||||||
|
|
||||||
ContainerState build(
|
ContainerState build(
|
||||||
SSHClient? client,
|
SSHClient? client,
|
||||||
@@ -42,187 +105,19 @@ abstract class _$ContainerNotifier
|
|||||||
String hostId,
|
String hostId,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
);
|
);
|
||||||
}
|
@$mustCallSuper
|
||||||
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
@ProviderFor(ContainerNotifier)
|
|
||||||
const containerNotifierProvider = ContainerNotifierFamily();
|
|
||||||
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
class ContainerNotifierFamily extends Family<ContainerState> {
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
const ContainerNotifierFamily();
|
|
||||||
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
ContainerNotifierProvider call(
|
|
||||||
SSHClient? client,
|
|
||||||
String userName,
|
|
||||||
String hostId,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
return ContainerNotifierProvider(client, userName, hostId, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ContainerNotifierProvider getProviderOverride(
|
void runBuild() {
|
||||||
covariant ContainerNotifierProvider provider,
|
final created = build(_$args.$1, _$args.$2, _$args.$3, _$args.$4);
|
||||||
) {
|
final ref = this.ref as $Ref<ContainerState, ContainerState>;
|
||||||
return call(
|
final element =
|
||||||
provider.client,
|
ref.element
|
||||||
provider.userName,
|
as $ClassProviderElement<
|
||||||
provider.hostId,
|
AnyNotifier<ContainerState, ContainerState>,
|
||||||
provider.context,
|
ContainerState,
|
||||||
);
|
Object?,
|
||||||
}
|
Object?
|
||||||
|
>;
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
element.handleValue(ref, created);
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'containerNotifierProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
class ContainerNotifierProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<ContainerNotifier, ContainerState> {
|
|
||||||
/// See also [ContainerNotifier].
|
|
||||||
ContainerNotifierProvider(
|
|
||||||
SSHClient? client,
|
|
||||||
String userName,
|
|
||||||
String hostId,
|
|
||||||
BuildContext context,
|
|
||||||
) : this._internal(
|
|
||||||
() => ContainerNotifier()
|
|
||||||
..client = client
|
|
||||||
..userName = userName
|
|
||||||
..hostId = hostId
|
|
||||||
..context = context,
|
|
||||||
from: containerNotifierProvider,
|
|
||||||
name: r'containerNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$containerNotifierHash,
|
|
||||||
dependencies: ContainerNotifierFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
ContainerNotifierFamily._allTransitiveDependencies,
|
|
||||||
client: client,
|
|
||||||
userName: userName,
|
|
||||||
hostId: hostId,
|
|
||||||
context: context,
|
|
||||||
);
|
|
||||||
|
|
||||||
ContainerNotifierProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.client,
|
|
||||||
required this.userName,
|
|
||||||
required this.hostId,
|
|
||||||
required this.context,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final SSHClient? client;
|
|
||||||
final String userName;
|
|
||||||
final String hostId;
|
|
||||||
final BuildContext context;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ContainerState runNotifierBuild(covariant ContainerNotifier notifier) {
|
|
||||||
return notifier.build(client, userName, hostId, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(ContainerNotifier Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: ContainerNotifierProvider._internal(
|
|
||||||
() => create()
|
|
||||||
..client = client
|
|
||||||
..userName = userName
|
|
||||||
..hostId = hostId
|
|
||||||
..context = context,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
client: client,
|
|
||||||
userName: userName,
|
|
||||||
hostId: hostId,
|
|
||||||
context: context,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
|
|
||||||
createElement() {
|
|
||||||
return _ContainerNotifierProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is ContainerNotifierProvider &&
|
|
||||||
other.client == client &&
|
|
||||||
other.userName == userName &&
|
|
||||||
other.hostId == hostId &&
|
|
||||||
other.context == context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, client.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, userName.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, hostId.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, context.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
mixin ContainerNotifierRef on AutoDisposeNotifierProviderRef<ContainerState> {
|
|
||||||
/// The parameter `client` of this provider.
|
|
||||||
SSHClient? get client;
|
|
||||||
|
|
||||||
/// The parameter `userName` of this provider.
|
|
||||||
String get userName;
|
|
||||||
|
|
||||||
/// The parameter `hostId` of this provider.
|
|
||||||
String get hostId;
|
|
||||||
|
|
||||||
/// The parameter `context` of this provider.
|
|
||||||
BuildContext get context;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ContainerNotifierProviderElement
|
|
||||||
extends
|
|
||||||
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
|
|
||||||
with ContainerNotifierRef {
|
|
||||||
_ContainerNotifierProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
SSHClient? get client => (origin as ContainerNotifierProvider).client;
|
|
||||||
@override
|
|
||||||
String get userName => (origin as ContainerNotifierProvider).userName;
|
|
||||||
@override
|
|
||||||
String get hostId => (origin as ContainerNotifierProvider).hostId;
|
|
||||||
@override
|
|
||||||
BuildContext get context => (origin as ContainerNotifierProvider).context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
|
||||||
|
|||||||
@@ -6,22 +6,59 @@ part of 'private_key.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$privateKeyNotifierHash() =>
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
r'404836a4409f64d305c1e22f4a57b52985a57b68';
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// See also [PrivateKeyNotifier].
|
|
||||||
@ProviderFor(PrivateKeyNotifier)
|
@ProviderFor(PrivateKeyNotifier)
|
||||||
final privateKeyNotifierProvider =
|
const privateKeyProvider = PrivateKeyNotifierProvider._();
|
||||||
NotifierProvider<PrivateKeyNotifier, PrivateKeyState>.internal(
|
|
||||||
PrivateKeyNotifier.new,
|
|
||||||
name: r'privateKeyNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$privateKeyNotifierHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$PrivateKeyNotifier = Notifier<PrivateKeyState>;
|
final class PrivateKeyNotifierProvider
|
||||||
// ignore_for_file: type=lint
|
extends $NotifierProvider<PrivateKeyNotifier, PrivateKeyState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
const PrivateKeyNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'privateKeyProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$privateKeyNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
PrivateKeyNotifier create() => PrivateKeyNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(PrivateKeyState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<PrivateKeyState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$privateKeyNotifierHash() =>
|
||||||
|
r'12edd05dca29d1cbc9e2a3e047c3d417d22f7bb7';
|
||||||
|
|
||||||
|
abstract class _$PrivateKeyNotifier extends $Notifier<PrivateKeyState> {
|
||||||
|
PrivateKeyState build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<PrivateKeyState, PrivateKeyState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<PrivateKeyState, PrivateKeyState>,
|
||||||
|
PrivateKeyState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_riverpod/misc.dart';
|
||||||
|
|
||||||
import 'package:server_box/data/provider/app.dart';
|
|
||||||
import 'package:server_box/data/provider/private_key.dart';
|
import 'package:server_box/data/provider/private_key.dart';
|
||||||
import 'package:server_box/data/provider/server/all.dart';
|
import 'package:server_box/data/provider/server/all.dart';
|
||||||
import 'package:server_box/data/provider/sftp.dart';
|
import 'package:server_box/data/provider/sftp.dart';
|
||||||
@@ -10,7 +11,7 @@ import 'package:server_box/data/provider/snippet.dart';
|
|||||||
/// ref.useNotifier, ref.readProvider, ref.watchProvider
|
/// ref.useNotifier, ref.readProvider, ref.watchProvider
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// - `providers.read.server` -> `ref.read(serversNotifierProvider)`
|
/// - `providers.read.server` -> `ref.read(serversProvider)`
|
||||||
/// - `providers.use.snippet` -> `ref.read(snippetsNotifierProvider.notifier)`
|
/// - `providers.use.snippet` -> `ref.read(snippetsNotifierProvider.notifier)`
|
||||||
|
|
||||||
extension RiverpodNotifiers on ConsumerState {
|
extension RiverpodNotifiers on ConsumerState {
|
||||||
@@ -45,11 +46,11 @@ final class ReadMyProvider {
|
|||||||
T call<T>(ProviderBase<T> provider) => ref.read(provider);
|
T call<T>(ProviderBase<T> provider) => ref.read(provider);
|
||||||
|
|
||||||
// Specific provider getters
|
// Specific provider getters
|
||||||
ServersState get server => ref.read(serversNotifierProvider);
|
ServersState get server => ref.read(serversProvider);
|
||||||
SnippetState get snippet => ref.read(snippetNotifierProvider);
|
SnippetState get snippet => ref.read(snippetProvider);
|
||||||
AppState get app => ref.read(appStatesProvider);
|
AppState get app => ref.read(appStatesProvider);
|
||||||
PrivateKeyState get privateKey => ref.read(privateKeyNotifierProvider);
|
PrivateKeyState get privateKey => ref.read(privateKeyProvider);
|
||||||
SftpState get sftp => ref.read(sftpNotifierProvider);
|
SftpState get sftp => ref.read(sftpProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class WatchMyProvider {
|
final class WatchMyProvider {
|
||||||
@@ -59,11 +60,11 @@ final class WatchMyProvider {
|
|||||||
T call<T>(ProviderBase<T> provider) => ref.watch(provider);
|
T call<T>(ProviderBase<T> provider) => ref.watch(provider);
|
||||||
|
|
||||||
// Specific provider getters
|
// Specific provider getters
|
||||||
ServersState get server => ref.watch(serversNotifierProvider);
|
ServersState get server => ref.watch(serversProvider);
|
||||||
SnippetState get snippet => ref.watch(snippetNotifierProvider);
|
SnippetState get snippet => ref.watch(snippetProvider);
|
||||||
AppState get app => ref.watch(appStatesProvider);
|
AppState get app => ref.watch(appStatesProvider);
|
||||||
PrivateKeyState get privateKey => ref.watch(privateKeyNotifierProvider);
|
PrivateKeyState get privateKey => ref.watch(privateKeyProvider);
|
||||||
SftpState get sftp => ref.watch(sftpNotifierProvider);
|
SftpState get sftp => ref.watch(sftpProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class UseNotifierMyProvider {
|
final class UseNotifierMyProvider {
|
||||||
@@ -74,9 +75,9 @@ final class UseNotifierMyProvider {
|
|||||||
ref.read(provider.notifier);
|
ref.read(provider.notifier);
|
||||||
|
|
||||||
// Specific provider notifier getters
|
// Specific provider notifier getters
|
||||||
ServersNotifier get server => ref.read(serversNotifierProvider.notifier);
|
ServersNotifier get server => ref.read(serversProvider.notifier);
|
||||||
SnippetNotifier get snippet => ref.read(snippetNotifierProvider.notifier);
|
SnippetNotifier get snippet => ref.read(snippetProvider.notifier);
|
||||||
AppStates get app => ref.read(appStatesProvider.notifier);
|
AppStates get app => ref.read(appStatesProvider.notifier);
|
||||||
PrivateKeyNotifier get privateKey => ref.read(privateKeyNotifierProvider.notifier);
|
PrivateKeyNotifier get privateKey => ref.read(privateKeyProvider.notifier);
|
||||||
SftpNotifier get sftp => ref.read(sftpNotifierProvider.notifier);
|
SftpNotifier get sftp => ref.read(sftpProvider.notifier);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ import 'package:dartssh2/dartssh2.dart';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:server_box/core/extension/context/locale.dart';
|
import 'package:server_box/core/extension/context/locale.dart';
|
||||||
@@ -45,7 +44,7 @@ class PveNotifier extends _$PveNotifier {
|
|||||||
@override
|
@override
|
||||||
PveState build(Spi spiParam) {
|
PveState build(Spi spiParam) {
|
||||||
spi = spiParam;
|
spi = spiParam;
|
||||||
final serverState = ref.watch(serverNotifierProvider(spi.id));
|
final serverState = ref.watch(serverProvider(spi.id));
|
||||||
final client = serverState.client;
|
final client = serverState.client;
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));
|
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));
|
||||||
@@ -108,7 +107,7 @@ class PveNotifier extends _$PveNotifier {
|
|||||||
final newUrl = Uri.parse(
|
final newUrl = Uri.parse(
|
||||||
addr,
|
addr,
|
||||||
).replace(host: 'localhost', port: _localPort).toString();
|
).replace(host: 'localhost', port: _localPort).toString();
|
||||||
debugPrint('Forwarding $newUrl to $addr');
|
dprint('Forwarding $newUrl to $addr');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,11 +234,15 @@ class PveNotifier extends _$PveNotifier {
|
|||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
try {
|
try {
|
||||||
await _serverSocket.close();
|
await _serverSocket.close();
|
||||||
} catch (_) {}
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to close server socket', e, s);
|
||||||
|
}
|
||||||
for (final forward in _forwards) {
|
for (final forward in _forwards) {
|
||||||
try {
|
try {
|
||||||
forward.close();
|
forward.close();
|
||||||
} catch (_) {}
|
} catch (e, s) {
|
||||||
|
Loggers.app.warning('Failed to close forward', e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,155 +6,96 @@ part of 'pve.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$pveNotifierHash() => r'b5da7240db1b9ee7d61f238cebca45821b7a3445';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
|
||||||
class _SystemHash {
|
|
||||||
_SystemHash._();
|
|
||||||
|
|
||||||
static int combine(int hash, int value) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + value);
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
|
||||||
return hash ^ (hash >> 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finish(int hash) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = hash ^ (hash >> 11);
|
|
||||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _$PveNotifier extends BuildlessAutoDisposeNotifier<PveState> {
|
|
||||||
late final Spi spiParam;
|
|
||||||
|
|
||||||
PveState build(Spi spiParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [PveNotifier].
|
|
||||||
@ProviderFor(PveNotifier)
|
@ProviderFor(PveNotifier)
|
||||||
const pveNotifierProvider = PveNotifierFamily();
|
const pveProvider = PveNotifierFamily._();
|
||||||
|
|
||||||
/// See also [PveNotifier].
|
final class PveNotifierProvider
|
||||||
class PveNotifierFamily extends Family<PveState> {
|
extends $NotifierProvider<PveNotifier, PveState> {
|
||||||
/// See also [PveNotifier].
|
const PveNotifierProvider._({
|
||||||
const PveNotifierFamily();
|
required PveNotifierFamily super.from,
|
||||||
|
required Spi super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'pveProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
/// See also [PveNotifier].
|
@override
|
||||||
PveNotifierProvider call(Spi spiParam) {
|
String debugGetCreateSourceHash() => _$pveNotifierHash();
|
||||||
return PveNotifierProvider(spiParam);
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'pveProvider'
|
||||||
|
''
|
||||||
|
'($argument)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
@override
|
@override
|
||||||
PveNotifierProvider getProviderOverride(
|
PveNotifier create() => PveNotifier();
|
||||||
covariant PveNotifierProvider provider,
|
|
||||||
) {
|
|
||||||
return call(provider.spiParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(PveState value) {
|
||||||
@override
|
return $ProviderOverride(
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'pveNotifierProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [PveNotifier].
|
|
||||||
class PveNotifierProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<PveNotifier, PveState> {
|
|
||||||
/// See also [PveNotifier].
|
|
||||||
PveNotifierProvider(Spi spiParam)
|
|
||||||
: this._internal(
|
|
||||||
() => PveNotifier()..spiParam = spiParam,
|
|
||||||
from: pveNotifierProvider,
|
|
||||||
name: r'pveNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$pveNotifierHash,
|
|
||||||
dependencies: PveNotifierFamily._dependencies,
|
|
||||||
allTransitiveDependencies: PveNotifierFamily._allTransitiveDependencies,
|
|
||||||
spiParam: spiParam,
|
|
||||||
);
|
|
||||||
|
|
||||||
PveNotifierProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.spiParam,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final Spi spiParam;
|
|
||||||
|
|
||||||
@override
|
|
||||||
PveState runNotifierBuild(covariant PveNotifier notifier) {
|
|
||||||
return notifier.build(spiParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(PveNotifier Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
origin: this,
|
||||||
override: PveNotifierProvider._internal(
|
providerOverride: $SyncValueProvider<PveState>(value),
|
||||||
() => create()..spiParam = spiParam,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
spiParam: spiParam,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<PveNotifier, PveState> createElement() {
|
|
||||||
return _PveNotifierProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is PveNotifierProvider && other.spiParam == spiParam;
|
return other is PveNotifierProvider && other.argument == argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
return argument.hashCode;
|
||||||
hash = _SystemHash.combine(hash, spiParam.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
String _$pveNotifierHash() => r'1e71faadee074b9c07bee731ef4ae6505e791967';
|
||||||
// ignore: unused_element
|
|
||||||
mixin PveNotifierRef on AutoDisposeNotifierProviderRef<PveState> {
|
|
||||||
/// The parameter `spiParam` of this provider.
|
|
||||||
Spi get spiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PveNotifierProviderElement
|
final class PveNotifierFamily extends $Family
|
||||||
extends AutoDisposeNotifierProviderElement<PveNotifier, PveState>
|
with $ClassFamilyOverride<PveNotifier, PveState, PveState, PveState, Spi> {
|
||||||
with PveNotifierRef {
|
const PveNotifierFamily._()
|
||||||
_PveNotifierProviderElement(super.provider);
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'pveProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
PveNotifierProvider call(Spi spiParam) =>
|
||||||
|
PveNotifierProvider._(argument: spiParam, from: this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Spi get spiParam => (origin as PveNotifierProvider).spiParam;
|
String toString() => r'pveProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
abstract class _$PveNotifier extends $Notifier<PveState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
late final _$args = ref.$arg as Spi;
|
||||||
|
Spi get spiParam => _$args;
|
||||||
|
|
||||||
|
PveState build(Spi spiParam);
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build(_$args);
|
||||||
|
final ref = this.ref as $Ref<PveState, PveState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<PveState, PveState>,
|
||||||
|
PveState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,42 +98,49 @@ class ServersNotifier extends _$ServersNotifier {
|
|||||||
if (spi != null) {
|
if (spi != null) {
|
||||||
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id);
|
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id);
|
||||||
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
|
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
|
||||||
final serverNotifier = ref.read(serverNotifierProvider(spi.id).notifier);
|
final serverNotifier = ref.read(serverProvider(spi.id).notifier);
|
||||||
await serverNotifier.refresh();
|
await serverNotifier.refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.wait(
|
final serversToRefresh = <MapEntry<String, Spi>>[];
|
||||||
state.servers.entries.map((entry) async {
|
final idsToResetLimiter = <String>[];
|
||||||
final serverId = entry.key;
|
|
||||||
final spi = entry.value;
|
|
||||||
|
|
||||||
if (onlyFailed) {
|
for (final entry in state.servers.entries) {
|
||||||
final serverState = ref.read(serverNotifierProvider(serverId));
|
final serverId = entry.key;
|
||||||
if (serverState.conn != ServerConn.failed) return;
|
final spi = entry.value;
|
||||||
TryLimiter.reset(serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.manualDisconnectedIds.contains(serverId)) return;
|
if (state.manualDisconnectedIds.contains(serverId)) continue;
|
||||||
|
|
||||||
final serverState = ref.read(serverNotifierProvider(serverId));
|
final serverState = ref.read(serverProvider(serverId));
|
||||||
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final serverNotifier = ref.read(serverNotifierProvider(serverId).notifier);
|
if (onlyFailed) {
|
||||||
await serverNotifier.refresh();
|
if (serverState.conn != ServerConn.failed) continue;
|
||||||
}),
|
idsToResetLimiter.add(serverId);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) continue;
|
||||||
|
|
||||||
|
serversToRefresh.add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final id in idsToResetLimiter) {
|
||||||
|
TryLimiter.reset(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final entry in serversToRefresh) {
|
||||||
|
final serverNotifier = ref.read(serverProvider(entry.key).notifier);
|
||||||
|
serverNotifier.refresh().ignore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startAutoRefresh() async {
|
Future<void> startAutoRefresh() async {
|
||||||
var duration = Stores.setting.serverStatusUpdateInterval.fetch();
|
var duration = Stores.setting.serverStatusUpdateInterval.fetch();
|
||||||
stopAutoRefresh();
|
stopAutoRefresh();
|
||||||
if (duration == 0) return;
|
if (duration == 0) return;
|
||||||
if (duration < 0 || duration > 10 || duration == 1) {
|
if (duration <= 1 || duration > 10) {
|
||||||
duration = 3;
|
|
||||||
Loggers.app.warning('Invalid duration: $duration, use default 3');
|
Loggers.app.warning('Invalid duration: $duration, use default 3');
|
||||||
|
duration = 3;
|
||||||
}
|
}
|
||||||
final timer = Timer.periodic(Duration(seconds: duration), (_) async {
|
final timer = Timer.periodic(Duration(seconds: duration), (_) async {
|
||||||
await refresh();
|
await refresh();
|
||||||
@@ -145,15 +152,15 @@ class ServersNotifier extends _$ServersNotifier {
|
|||||||
final timer = state.autoRefreshTimer;
|
final timer = state.autoRefreshTimer;
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
state = state.copyWith(autoRefreshTimer: null);
|
|
||||||
}
|
}
|
||||||
|
state = state.copyWith(autoRefreshTimer: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isAutoRefreshOn => state.autoRefreshTimer != null;
|
bool get isAutoRefreshOn => state.autoRefreshTimer != null;
|
||||||
|
|
||||||
void setDisconnected() {
|
void setDisconnected() {
|
||||||
for (final serverId in state.servers.keys) {
|
for (final serverId in state.servers.keys) {
|
||||||
final serverNotifier = ref.read(serverNotifierProvider(serverId).notifier);
|
final serverNotifier = ref.read(serverProvider(serverId).notifier);
|
||||||
serverNotifier.updateConnection(ServerConn.disconnected);
|
serverNotifier.updateConnection(ServerConn.disconnected);
|
||||||
|
|
||||||
// Update SSH session status to disconnected
|
// Update SSH session status to disconnected
|
||||||
@@ -180,7 +187,7 @@ class ServersNotifier extends _$ServersNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverNotifier = ref.read(serverNotifierProvider(id).notifier);
|
final serverNotifier = ref.read(serverProvider(id).notifier);
|
||||||
serverNotifier.closeConnection();
|
serverNotifier.closeConnection();
|
||||||
|
|
||||||
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id);
|
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id);
|
||||||
@@ -239,6 +246,50 @@ class ServersNotifier extends _$ServersNotifier {
|
|||||||
bakSync.sync(milliDelay: 1000);
|
bakSync.sync(milliDelay: 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateServerOrder(List<String> order) {
|
||||||
|
final seen = <String>{};
|
||||||
|
final newOrder = <String>[];
|
||||||
|
|
||||||
|
for (final id in order) {
|
||||||
|
if (!state.servers.containsKey(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!seen.add(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
newOrder.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final id in state.servers.keys) {
|
||||||
|
if (seen.add(id)) {
|
||||||
|
newOrder.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isSameOrder(newOrder, state.serverOrder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.copyWith(serverOrder: newOrder);
|
||||||
|
Stores.setting.serverOrder.put(newOrder);
|
||||||
|
bakSync.sync(milliDelay: 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isSameOrder(List<String> a, List<String> b) {
|
||||||
|
if (identical(a, b)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.length != b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] != b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateServer(Spi old, Spi newSpi) async {
|
Future<void> updateServer(Spi old, Spi newSpi) async {
|
||||||
if (old != newSpi) {
|
if (old != newSpi) {
|
||||||
Stores.server.update(old, newSpi);
|
Stores.server.update(old, newSpi);
|
||||||
@@ -259,7 +310,7 @@ class ServersNotifier extends _$ServersNotifier {
|
|||||||
} else {
|
} else {
|
||||||
newServers[old.id] = newSpi;
|
newServers[old.id] = newSpi;
|
||||||
// Update SPI in the corresponding IndividualServerNotifier
|
// Update SPI in the corresponding IndividualServerNotifier
|
||||||
final serverNotifier = ref.read(serverNotifierProvider(old.id).notifier);
|
final serverNotifier = ref.read(serverProvider(old.id).notifier);
|
||||||
serverNotifier.updateSpi(newSpi);
|
serverNotifier.updateSpi(newSpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,58 @@ part of 'all.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$serversNotifierHash() => r'2ae641188f772794a32e8700c008f51ba0cc1ec9';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// See also [ServersNotifier].
|
|
||||||
@ProviderFor(ServersNotifier)
|
@ProviderFor(ServersNotifier)
|
||||||
final serversNotifierProvider =
|
const serversProvider = ServersNotifierProvider._();
|
||||||
NotifierProvider<ServersNotifier, ServersState>.internal(
|
|
||||||
ServersNotifier.new,
|
|
||||||
name: r'serversNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$serversNotifierHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$ServersNotifier = Notifier<ServersState>;
|
final class ServersNotifierProvider
|
||||||
// ignore_for_file: type=lint
|
extends $NotifierProvider<ServersNotifier, ServersState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
const ServersNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'serversProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$serversNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
ServersNotifier create() => ServersNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ServersState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<ServersState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$serversNotifierHash() => r'277d1b219235f14bcc1b82a1e16260c2f28decdb';
|
||||||
|
|
||||||
|
abstract class _$ServersNotifier extends $Notifier<ServersState> {
|
||||||
|
ServersState build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<ServersState, ServersState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<ServersState, ServersState>,
|
||||||
|
ServersState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:computer/computer.dart';
|
import 'package:computer/computer.dart';
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
import 'package:flutter_gbk2utf8/flutter_gbk2utf8.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:server_box/core/extension/ssh_client.dart';
|
import 'package:server_box/core/extension/ssh_client.dart';
|
||||||
import 'package:server_box/core/utils/server.dart';
|
import 'package:server_box/core/utils/server.dart';
|
||||||
import 'package:server_box/core/utils/ssh_auth.dart';
|
import 'package:server_box/core/utils/ssh_auth.dart';
|
||||||
|
import 'package:server_box/data/helper/ssh_decoder.dart';
|
||||||
import 'package:server_box/data/helper/system_detector.dart';
|
import 'package:server_box/data/helper/system_detector.dart';
|
||||||
import 'package:server_box/data/model/app/error.dart';
|
import 'package:server_box/data/model/app/error.dart';
|
||||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||||
import 'package:server_box/data/model/app/scripts/shell_func.dart';
|
import 'package:server_box/data/model/app/scripts/shell_func.dart';
|
||||||
|
import 'package:server_box/data/model/server/connection_stat.dart';
|
||||||
import 'package:server_box/data/model/server/server.dart';
|
import 'package:server_box/data/model/server/server.dart';
|
||||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||||
import 'package:server_box/data/model/server/server_status_update_req.dart';
|
import 'package:server_box/data/model/server/server_status_update_req.dart';
|
||||||
@@ -35,16 +35,15 @@ abstract class ServerState with _$ServerState {
|
|||||||
required ServerStatus status,
|
required ServerStatus status,
|
||||||
@Default(ServerConn.disconnected) ServerConn conn,
|
@Default(ServerConn.disconnected) ServerConn conn,
|
||||||
SSHClient? client,
|
SSHClient? client,
|
||||||
Future<void>? updateFuture,
|
|
||||||
}) = _ServerState;
|
}) = _ServerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Individual server state management
|
// Individual server state management
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
class ServerNotifier extends _$ServerNotifier {
|
class ServerNotifier extends _$ServerNotifier {
|
||||||
@override
|
@override
|
||||||
ServerState build(String serverId) {
|
ServerState build(String serverId) {
|
||||||
final serverNotifier = ref.read(serversNotifierProvider);
|
final serverNotifier = ref.read(serversProvider);
|
||||||
final spi = serverNotifier.servers[serverId];
|
final spi = serverNotifier.servers[serverId];
|
||||||
if (spi == null) {
|
if (spi == null) {
|
||||||
throw StateError('Server $serverId not found');
|
throw StateError('Server $serverId not found');
|
||||||
@@ -81,19 +80,16 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh server status
|
// Refresh server status
|
||||||
|
bool _isRefreshing = false;
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
if (state.updateFuture != null) {
|
if (_isRefreshing) return;
|
||||||
await state.updateFuture;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final updateFuture = _updateServer();
|
|
||||||
state = state.copyWith(updateFuture: updateFuture);
|
|
||||||
|
|
||||||
|
_isRefreshing = true;
|
||||||
try {
|
try {
|
||||||
await updateFuture;
|
await _updateServer();
|
||||||
} finally {
|
} finally {
|
||||||
state = state.copyWith(updateFuture: null);
|
_isRefreshing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,23 +135,55 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
|
|
||||||
final time2 = DateTime.now();
|
final time2 = DateTime.now();
|
||||||
final spentTime = time2.difference(time1).inMilliseconds;
|
final spentTime = time2.difference(time1).inMilliseconds;
|
||||||
if (spi.jumpId == null) {
|
if ((spi.jumpChainIds?.isNotEmpty != true) && spi.jumpId == null) {
|
||||||
Loggers.app.info('Connected to ${spi.name} in $spentTime ms.');
|
Loggers.app.info('Connected to ${spi.name} in $spentTime ms.');
|
||||||
} else {
|
} else {
|
||||||
Loggers.app.info('Jump to ${spi.name} in $spentTime ms.');
|
Loggers.app.info('Jump to ${spi.name} in $spentTime ms.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record successful connection
|
||||||
|
Stores.connectionStats.recordConnection(ConnectionStat(
|
||||||
|
serverId: spi.id,
|
||||||
|
serverName: spi.name,
|
||||||
|
timestamp: time1,
|
||||||
|
result: ConnectionResult.success,
|
||||||
|
durationMs: spentTime,
|
||||||
|
));
|
||||||
|
|
||||||
final sessionId = 'ssh_${spi.id}';
|
final sessionId = 'ssh_${spi.id}';
|
||||||
TermSessionManager.add(
|
TermSessionManager.add(
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
spi: spi,
|
spi: spi,
|
||||||
startTimeMs: time1.millisecondsSinceEpoch,
|
startTimeMs: time1.millisecondsSinceEpoch,
|
||||||
disconnect: () => ref.read(serversNotifierProvider.notifier).closeOneServer(spi.id),
|
disconnect: () => ref.read(serversProvider.notifier).closeOneServer(spi.id),
|
||||||
status: TermSessionStatus.connecting,
|
status: TermSessionStatus.connecting,
|
||||||
);
|
);
|
||||||
TermSessionManager.setActive(sessionId, hasTerminal: false);
|
TermSessionManager.setActive(sessionId, hasTerminal: false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
|
|
||||||
|
// Determine connection failure type
|
||||||
|
ConnectionResult failureResult;
|
||||||
|
if (e.toString().contains('timeout') || e.toString().contains('Timeout')) {
|
||||||
|
failureResult = ConnectionResult.timeout;
|
||||||
|
} else if (e.toString().contains('auth') || e.toString().contains('Authentication')) {
|
||||||
|
failureResult = ConnectionResult.authFailed;
|
||||||
|
} else if (e.toString().contains('network') || e.toString().contains('Network')) {
|
||||||
|
failureResult = ConnectionResult.networkError;
|
||||||
|
} else {
|
||||||
|
failureResult = ConnectionResult.unknownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record failed connection
|
||||||
|
Stores.connectionStats.recordConnection(ConnectionStat(
|
||||||
|
serverId: spi.id,
|
||||||
|
serverName: spi.name,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
result: failureResult,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
durationMs: 0,
|
||||||
|
));
|
||||||
|
|
||||||
final newStatus = state.status..err = SSHErr(type: SSHErrType.connect, message: e.toString());
|
final newStatus = state.status..err = SSHErr(type: SSHErrType.connect, message: e.toString());
|
||||||
updateStatus(newStatus);
|
updateStatus(newStatus);
|
||||||
updateConnection(ServerConn.failed);
|
updateConnection(ServerConn.failed);
|
||||||
@@ -180,7 +208,9 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
final newStatus = state.status..system = detectedSystemType;
|
final newStatus = state.status..system = detectedSystemType;
|
||||||
updateStatus(newStatus);
|
updateStatus(newStatus);
|
||||||
|
|
||||||
final (_, writeScriptResult) = await state.client!.exec(
|
Loggers.app.info('Writing script for ${spi.name} (${detectedSystemType.name})');
|
||||||
|
|
||||||
|
final (stdoutResult, writeScriptResult) = await state.client!.execSafe(
|
||||||
(session) async {
|
(session) async {
|
||||||
final scriptRaw = ShellFuncManager.allScript(
|
final scriptRaw = ShellFuncManager.allScript(
|
||||||
spi.custom?.cmds,
|
spi.custom?.cmds,
|
||||||
@@ -195,10 +225,22 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
systemType: detectedSystemType,
|
systemType: detectedSystemType,
|
||||||
customDir: spi.custom?.scriptDir,
|
customDir: spi.custom?.scriptDir,
|
||||||
),
|
),
|
||||||
|
systemType: detectedSystemType,
|
||||||
|
context: 'WriteScript<${spi.name}>',
|
||||||
);
|
);
|
||||||
if (writeScriptResult.isNotEmpty && detectedSystemType != SystemType.windows) {
|
|
||||||
ShellFuncManager.switchScriptDir(spi.id, systemType: detectedSystemType);
|
if (stdoutResult.isNotEmpty) {
|
||||||
throw writeScriptResult;
|
Loggers.app.info('Script write stdout for ${spi.name}: $stdoutResult');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeScriptResult.isNotEmpty) {
|
||||||
|
Loggers.app.warning('Script write stderr for ${spi.name}: $writeScriptResult');
|
||||||
|
if (detectedSystemType != SystemType.windows) {
|
||||||
|
ShellFuncManager.switchScriptDir(spi.id, systemType: detectedSystemType);
|
||||||
|
throw writeScriptResult;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Loggers.app.info('Script written successfully for ${spi.name}');
|
||||||
}
|
}
|
||||||
} on SSHAuthAbortError catch (e) {
|
} on SSHAuthAbortError catch (e) {
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
@@ -245,43 +287,25 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
String? raw;
|
String? raw;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final execResult = await state.client?.run(
|
final statusCmd = ShellFunc.status.exec(spi.id, systemType: state.status.system, customDir: spi.custom?.scriptDir);
|
||||||
ShellFunc.status.exec(spi.id, systemType: state.status.system, customDir: spi.custom?.scriptDir),
|
// Loggers.app.info('Running status command for ${spi.name} (${state.status.system.name}): $statusCmd');
|
||||||
);
|
final execResult = await state.client?.run(statusCmd);
|
||||||
if (execResult != null) {
|
if (execResult != null) {
|
||||||
String? rawStr;
|
raw = SSHDecoder.decode(
|
||||||
bool needGbk = false;
|
execResult,
|
||||||
try {
|
isWindows: state.status.system == SystemType.windows,
|
||||||
rawStr = utf8.decode(execResult, allowMalformed: true);
|
context: 'GetStatus<${spi.name}>',
|
||||||
// If there are unparseable characters, try fallback to GBK decoding
|
);
|
||||||
if (rawStr.contains('<EFBFBD>')) {
|
// Loggers.app.info('Status response length for ${spi.name}: ${raw.length} bytes');
|
||||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding');
|
|
||||||
needGbk = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding', e);
|
|
||||||
needGbk = true;
|
|
||||||
}
|
|
||||||
if (needGbk) {
|
|
||||||
try {
|
|
||||||
rawStr = gbk.decode(execResult);
|
|
||||||
} catch (e2) {
|
|
||||||
Loggers.app.warning('GBK decoding failed', e2);
|
|
||||||
rawStr = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawStr == null) {
|
|
||||||
Loggers.app.warning('Decoding failed, execResult: $execResult');
|
|
||||||
}
|
|
||||||
raw = rawStr;
|
|
||||||
} else {
|
} else {
|
||||||
raw = execResult.toString();
|
raw = '';
|
||||||
|
Loggers.app.warning('No status result from ${spi.name}');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw == null || raw.isEmpty) {
|
if (raw.isEmpty) {
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
final newStatus = state.status
|
final newStatus = state.status
|
||||||
..err = SSHErr(type: SSHErrType.segements, message: 'decode or split failed, raw:\n$raw');
|
..err = SSHErr(type: SSHErrType.segements, message: 'Empty response from server');
|
||||||
updateStatus(newStatus);
|
updateStatus(newStatus);
|
||||||
updateConnection(ServerConn.failed);
|
updateConnection(ServerConn.failed);
|
||||||
|
|
||||||
@@ -291,7 +315,7 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
segments = raw.split(ScriptConstants.separator).map((e) => e.trim()).toList();
|
segments = raw.split(ScriptConstants.separator).map((e) => e.trim()).toList();
|
||||||
if (raw.isEmpty || segments.isEmpty) {
|
if (segments.isEmpty) {
|
||||||
if (Stores.setting.keepStatusWhenErr.fetch()) {
|
if (Stores.setting.keepStatusWhenErr.fetch()) {
|
||||||
// Keep previous server status when error occurs
|
// Keep previous server status when error occurs
|
||||||
if (state.conn != ServerConn.failed && state.status.more.isNotEmpty) {
|
if (state.conn != ServerConn.failed && state.status.more.isNotEmpty) {
|
||||||
@@ -300,7 +324,7 @@ class ServerNotifier extends _$ServerNotifier {
|
|||||||
}
|
}
|
||||||
TryLimiter.inc(sid);
|
TryLimiter.inc(sid);
|
||||||
final newStatus = state.status
|
final newStatus = state.status
|
||||||
..err = SSHErr(type: SSHErrType.segements, message: 'Seperate segments failed, raw:\n$raw');
|
..err = SSHErr(type: SSHErrType.segements, message: 'Separate segments failed, raw:\n$raw');
|
||||||
updateStatus(newStatus);
|
updateStatus(newStatus);
|
||||||
updateConnection(ServerConn.failed);
|
updateConnection(ServerConn.failed);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$ServerState {
|
mixin _$ServerState {
|
||||||
|
|
||||||
Spi get spi; ServerStatus get status; ServerConn get conn; SSHClient? get client; Future<void>? get updateFuture;
|
Spi get spi; ServerStatus get status; ServerConn get conn; SSHClient? get client;
|
||||||
/// Create a copy of ServerState
|
/// Create a copy of ServerState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -25,16 +25,16 @@ $ServerStateCopyWith<ServerState> get copyWith => _$ServerStateCopyWithImpl<Serv
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)&&(identical(other.updateFuture, updateFuture) || other.updateFuture == updateFuture));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,spi,status,conn,client,updateFuture);
|
int get hashCode => Object.hash(runtimeType,spi,status,conn,client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)';
|
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ abstract mixin class $ServerStateCopyWith<$Res> {
|
|||||||
factory $ServerStateCopyWith(ServerState value, $Res Function(ServerState) _then) = _$ServerStateCopyWithImpl;
|
factory $ServerStateCopyWith(ServerState value, $Res Function(ServerState) _then) = _$ServerStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture
|
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -62,14 +62,13 @@ class _$ServerStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ServerState
|
/// Create a copy of ServerState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
||||||
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
||||||
as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable
|
as SSHClient?,
|
||||||
as Future<void>?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of ServerState
|
/// Create a copy of ServerState
|
||||||
@@ -163,10 +162,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ServerState() when $default != null:
|
case _ServerState() when $default != null:
|
||||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
return $default(_that.spi,_that.status,_that.conn,_that.client);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -184,10 +183,10 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ServerState():
|
case _ServerState():
|
||||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
return $default(_that.spi,_that.status,_that.conn,_that.client);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -204,10 +203,10 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _ServerState() when $default != null:
|
case _ServerState() when $default != null:
|
||||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
return $default(_that.spi,_that.status,_that.conn,_that.client);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -219,14 +218,13 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur
|
|||||||
|
|
||||||
|
|
||||||
class _ServerState implements ServerState {
|
class _ServerState implements ServerState {
|
||||||
const _ServerState({required this.spi, required this.status, this.conn = ServerConn.disconnected, this.client, this.updateFuture});
|
const _ServerState({required this.spi, required this.status, this.conn = ServerConn.disconnected, this.client});
|
||||||
|
|
||||||
|
|
||||||
@override final Spi spi;
|
@override final Spi spi;
|
||||||
@override final ServerStatus status;
|
@override final ServerStatus status;
|
||||||
@override@JsonKey() final ServerConn conn;
|
@override@JsonKey() final ServerConn conn;
|
||||||
@override final SSHClient? client;
|
@override final SSHClient? client;
|
||||||
@override final Future<void>? updateFuture;
|
|
||||||
|
|
||||||
/// Create a copy of ServerState
|
/// Create a copy of ServerState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -238,16 +236,16 @@ _$ServerStateCopyWith<_ServerState> get copyWith => __$ServerStateCopyWithImpl<_
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)&&(identical(other.updateFuture, updateFuture) || other.updateFuture == updateFuture));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,spi,status,conn,client,updateFuture);
|
int get hashCode => Object.hash(runtimeType,spi,status,conn,client);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)';
|
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -258,7 +256,7 @@ abstract mixin class _$ServerStateCopyWith<$Res> implements $ServerStateCopyWith
|
|||||||
factory _$ServerStateCopyWith(_ServerState value, $Res Function(_ServerState) _then) = __$ServerStateCopyWithImpl;
|
factory _$ServerStateCopyWith(_ServerState value, $Res Function(_ServerState) _then) = __$ServerStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture
|
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -275,14 +273,13 @@ class __$ServerStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of ServerState
|
/// Create a copy of ServerState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,}) {
|
||||||
return _then(_ServerState(
|
return _then(_ServerState(
|
||||||
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
||||||
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
||||||
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
||||||
as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable
|
as SSHClient?,
|
||||||
as Future<void>?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,158 +6,103 @@ part of 'single.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$serverNotifierHash() => r'5625b0a4762c28efdbc124809c03b84a51d213b1';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
|
||||||
class _SystemHash {
|
|
||||||
_SystemHash._();
|
|
||||||
|
|
||||||
static int combine(int hash, int value) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + value);
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
|
||||||
return hash ^ (hash >> 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finish(int hash) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = hash ^ (hash >> 11);
|
|
||||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _$ServerNotifier
|
|
||||||
extends BuildlessAutoDisposeNotifier<ServerState> {
|
|
||||||
late final String serverId;
|
|
||||||
|
|
||||||
ServerState build(String serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [ServerNotifier].
|
|
||||||
@ProviderFor(ServerNotifier)
|
@ProviderFor(ServerNotifier)
|
||||||
const serverNotifierProvider = ServerNotifierFamily();
|
const serverProvider = ServerNotifierFamily._();
|
||||||
|
|
||||||
/// See also [ServerNotifier].
|
final class ServerNotifierProvider
|
||||||
class ServerNotifierFamily extends Family<ServerState> {
|
extends $NotifierProvider<ServerNotifier, ServerState> {
|
||||||
/// See also [ServerNotifier].
|
const ServerNotifierProvider._({
|
||||||
const ServerNotifierFamily();
|
required ServerNotifierFamily super.from,
|
||||||
|
required String super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'serverProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
/// See also [ServerNotifier].
|
@override
|
||||||
ServerNotifierProvider call(String serverId) {
|
String debugGetCreateSourceHash() => _$serverNotifierHash();
|
||||||
return ServerNotifierProvider(serverId);
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'serverProvider'
|
||||||
|
''
|
||||||
|
'($argument)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
@override
|
@override
|
||||||
ServerNotifierProvider getProviderOverride(
|
ServerNotifier create() => ServerNotifier();
|
||||||
covariant ServerNotifierProvider provider,
|
|
||||||
) {
|
|
||||||
return call(provider.serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(ServerState value) {
|
||||||
@override
|
return $ProviderOverride(
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'serverNotifierProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [ServerNotifier].
|
|
||||||
class ServerNotifierProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<ServerNotifier, ServerState> {
|
|
||||||
/// See also [ServerNotifier].
|
|
||||||
ServerNotifierProvider(String serverId)
|
|
||||||
: this._internal(
|
|
||||||
() => ServerNotifier()..serverId = serverId,
|
|
||||||
from: serverNotifierProvider,
|
|
||||||
name: r'serverNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$serverNotifierHash,
|
|
||||||
dependencies: ServerNotifierFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
ServerNotifierFamily._allTransitiveDependencies,
|
|
||||||
serverId: serverId,
|
|
||||||
);
|
|
||||||
|
|
||||||
ServerNotifierProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.serverId,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final String serverId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ServerState runNotifierBuild(covariant ServerNotifier notifier) {
|
|
||||||
return notifier.build(serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(ServerNotifier Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
origin: this,
|
||||||
override: ServerNotifierProvider._internal(
|
providerOverride: $SyncValueProvider<ServerState>(value),
|
||||||
() => create()..serverId = serverId,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
serverId: serverId,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<ServerNotifier, ServerState>
|
|
||||||
createElement() {
|
|
||||||
return _ServerNotifierProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is ServerNotifierProvider && other.serverId == serverId;
|
return other is ServerNotifierProvider && other.argument == argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
return argument.hashCode;
|
||||||
hash = _SystemHash.combine(hash, serverId.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
String _$serverNotifierHash() => r'52e806bcc32a7818d1ec2b07a3c683b06885c9f8';
|
||||||
// ignore: unused_element
|
|
||||||
mixin ServerNotifierRef on AutoDisposeNotifierProviderRef<ServerState> {
|
|
||||||
/// The parameter `serverId` of this provider.
|
|
||||||
String get serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ServerNotifierProviderElement
|
final class ServerNotifierFamily extends $Family
|
||||||
extends AutoDisposeNotifierProviderElement<ServerNotifier, ServerState>
|
with
|
||||||
with ServerNotifierRef {
|
$ClassFamilyOverride<
|
||||||
_ServerNotifierProviderElement(super.provider);
|
ServerNotifier,
|
||||||
|
ServerState,
|
||||||
|
ServerState,
|
||||||
|
ServerState,
|
||||||
|
String
|
||||||
|
> {
|
||||||
|
const ServerNotifierFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'serverProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
ServerNotifierProvider call(String serverId) =>
|
||||||
|
ServerNotifierProvider._(argument: serverId, from: this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get serverId => (origin as ServerNotifierProvider).serverId;
|
String toString() => r'serverProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
abstract class _$ServerNotifier extends $Notifier<ServerState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
late final _$args = ref.$arg as String;
|
||||||
|
String get serverId => _$args;
|
||||||
|
|
||||||
|
ServerState build(String serverId);
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build(_$args);
|
||||||
|
final ref = this.ref as $Ref<ServerState, ServerState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<ServerState, ServerState>,
|
||||||
|
ServerState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,20 +6,58 @@ part of 'sftp.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(SftpNotifier)
|
||||||
|
const sftpProvider = SftpNotifierProvider._();
|
||||||
|
|
||||||
|
final class SftpNotifierProvider
|
||||||
|
extends $NotifierProvider<SftpNotifier, SftpState> {
|
||||||
|
const SftpNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'sftpProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$sftpNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
SftpNotifier create() => SftpNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(SftpState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<SftpState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _$sftpNotifierHash() => r'f8412a4bd1f2bc5919ec31a3eba1c27e9a578f41';
|
String _$sftpNotifierHash() => r'f8412a4bd1f2bc5919ec31a3eba1c27e9a578f41';
|
||||||
|
|
||||||
/// See also [SftpNotifier].
|
abstract class _$SftpNotifier extends $Notifier<SftpState> {
|
||||||
@ProviderFor(SftpNotifier)
|
SftpState build();
|
||||||
final sftpNotifierProvider = NotifierProvider<SftpNotifier, SftpState>.internal(
|
@$mustCallSuper
|
||||||
SftpNotifier.new,
|
@override
|
||||||
name: r'sftpNotifierProvider',
|
void runBuild() {
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
final created = build();
|
||||||
? null
|
final ref = this.ref as $Ref<SftpState, SftpState>;
|
||||||
: _$sftpNotifierHash,
|
final element =
|
||||||
dependencies: null,
|
ref.element
|
||||||
allTransitiveDependencies: null,
|
as $ClassProviderElement<
|
||||||
);
|
AnyNotifier<SftpState, SftpState>,
|
||||||
|
SftpState,
|
||||||
typedef _$SftpNotifier = Notifier<SftpState>;
|
Object?,
|
||||||
// ignore_for_file: type=lint
|
Object?
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,21 +6,58 @@ part of 'snippet.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$snippetNotifierHash() => r'caf0361f9a0346fb99cb90f032f1ceb29446dd71';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// See also [SnippetNotifier].
|
|
||||||
@ProviderFor(SnippetNotifier)
|
@ProviderFor(SnippetNotifier)
|
||||||
final snippetNotifierProvider =
|
const snippetProvider = SnippetNotifierProvider._();
|
||||||
NotifierProvider<SnippetNotifier, SnippetState>.internal(
|
|
||||||
SnippetNotifier.new,
|
|
||||||
name: r'snippetNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$snippetNotifierHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$SnippetNotifier = Notifier<SnippetState>;
|
final class SnippetNotifierProvider
|
||||||
// ignore_for_file: type=lint
|
extends $NotifierProvider<SnippetNotifier, SnippetState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
const SnippetNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'snippetProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$snippetNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
SnippetNotifier create() => SnippetNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(SnippetState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<SnippetState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$snippetNotifierHash() => r'8285c7edf905a4aaa41cd8b65b0a6755c8b97fc9';
|
||||||
|
|
||||||
|
abstract class _$SnippetNotifier extends $Notifier<SnippetState> {
|
||||||
|
SnippetState build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<SnippetState, SnippetState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<SnippetState, SnippetState>,
|
||||||
|
SnippetState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class SystemdNotifier extends _$SystemdNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
SystemdState build(Spi spi) {
|
SystemdState build(Spi spi) {
|
||||||
final si = ref.read(serverNotifierProvider(spi.id));
|
final si = ref.read(serverProvider(spi.id));
|
||||||
_si = si;
|
_si = si;
|
||||||
// Async initialization
|
// Async initialization
|
||||||
Future.microtask(() => getUnits());
|
Future.microtask(() => getUnits());
|
||||||
|
|||||||
@@ -6,158 +6,103 @@ part of 'systemd.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$systemdNotifierHash() => r'98466bd176518545be49cae52f8dbe12af3a88a6';
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
|
||||||
class _SystemHash {
|
|
||||||
_SystemHash._();
|
|
||||||
|
|
||||||
static int combine(int hash, int value) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + value);
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
|
||||||
return hash ^ (hash >> 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int finish(int hash) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
hash = hash ^ (hash >> 11);
|
|
||||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _$SystemdNotifier
|
|
||||||
extends BuildlessAutoDisposeNotifier<SystemdState> {
|
|
||||||
late final Spi spi;
|
|
||||||
|
|
||||||
SystemdState build(Spi spi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [SystemdNotifier].
|
|
||||||
@ProviderFor(SystemdNotifier)
|
@ProviderFor(SystemdNotifier)
|
||||||
const systemdNotifierProvider = SystemdNotifierFamily();
|
const systemdProvider = SystemdNotifierFamily._();
|
||||||
|
|
||||||
/// See also [SystemdNotifier].
|
final class SystemdNotifierProvider
|
||||||
class SystemdNotifierFamily extends Family<SystemdState> {
|
extends $NotifierProvider<SystemdNotifier, SystemdState> {
|
||||||
/// See also [SystemdNotifier].
|
const SystemdNotifierProvider._({
|
||||||
const SystemdNotifierFamily();
|
required SystemdNotifierFamily super.from,
|
||||||
|
required Spi super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'systemdProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
/// See also [SystemdNotifier].
|
@override
|
||||||
SystemdNotifierProvider call(Spi spi) {
|
String debugGetCreateSourceHash() => _$systemdNotifierHash();
|
||||||
return SystemdNotifierProvider(spi);
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'systemdProvider'
|
||||||
|
''
|
||||||
|
'($argument)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
@override
|
@override
|
||||||
SystemdNotifierProvider getProviderOverride(
|
SystemdNotifier create() => SystemdNotifier();
|
||||||
covariant SystemdNotifierProvider provider,
|
|
||||||
) {
|
|
||||||
return call(provider.spi);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(SystemdState value) {
|
||||||
@override
|
return $ProviderOverride(
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'systemdNotifierProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [SystemdNotifier].
|
|
||||||
class SystemdNotifierProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<SystemdNotifier, SystemdState> {
|
|
||||||
/// See also [SystemdNotifier].
|
|
||||||
SystemdNotifierProvider(Spi spi)
|
|
||||||
: this._internal(
|
|
||||||
() => SystemdNotifier()..spi = spi,
|
|
||||||
from: systemdNotifierProvider,
|
|
||||||
name: r'systemdNotifierProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$systemdNotifierHash,
|
|
||||||
dependencies: SystemdNotifierFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
SystemdNotifierFamily._allTransitiveDependencies,
|
|
||||||
spi: spi,
|
|
||||||
);
|
|
||||||
|
|
||||||
SystemdNotifierProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.spi,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final Spi spi;
|
|
||||||
|
|
||||||
@override
|
|
||||||
SystemdState runNotifierBuild(covariant SystemdNotifier notifier) {
|
|
||||||
return notifier.build(spi);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(SystemdNotifier Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SystemdNotifierProvider._internal(
|
providerOverride: $SyncValueProvider<SystemdState>(value),
|
||||||
() => create()..spi = spi,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
spi: spi,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState>
|
|
||||||
createElement() {
|
|
||||||
return _SystemdNotifierProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SystemdNotifierProvider && other.spi == spi;
|
return other is SystemdNotifierProvider && other.argument == argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
return argument.hashCode;
|
||||||
hash = _SystemHash.combine(hash, spi.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
String _$systemdNotifierHash() => r'030d556efc3d897419cd3462d37cb705813e24c7';
|
||||||
// ignore: unused_element
|
|
||||||
mixin SystemdNotifierRef on AutoDisposeNotifierProviderRef<SystemdState> {
|
|
||||||
/// The parameter `spi` of this provider.
|
|
||||||
Spi get spi;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SystemdNotifierProviderElement
|
final class SystemdNotifierFamily extends $Family
|
||||||
extends AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState>
|
with
|
||||||
with SystemdNotifierRef {
|
$ClassFamilyOverride<
|
||||||
_SystemdNotifierProviderElement(super.provider);
|
SystemdNotifier,
|
||||||
|
SystemdState,
|
||||||
|
SystemdState,
|
||||||
|
SystemdState,
|
||||||
|
Spi
|
||||||
|
> {
|
||||||
|
const SystemdNotifierFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'systemdProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
SystemdNotifierProvider call(Spi spi) =>
|
||||||
|
SystemdNotifierProvider._(argument: spi, from: this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Spi get spi => (origin as SystemdNotifierProvider).spi;
|
String toString() => r'systemdProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
abstract class _$SystemdNotifier extends $Notifier<SystemdState> {
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
late final _$args = ref.$arg as Spi;
|
||||||
|
Spi get spi => _$args;
|
||||||
|
|
||||||
|
SystemdState build(Spi spi);
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build(_$args);
|
||||||
|
final ref = this.ref as $Ref<SystemdState, SystemdState>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<SystemdState, SystemdState>,
|
||||||
|
SystemdState,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,21 +6,58 @@ part of 'virtual_keyboard.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(VirtKeyboard)
|
||||||
|
const virtKeyboardProvider = VirtKeyboardProvider._();
|
||||||
|
|
||||||
|
final class VirtKeyboardProvider
|
||||||
|
extends $NotifierProvider<VirtKeyboard, VirtKeyState> {
|
||||||
|
const VirtKeyboardProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'virtKeyboardProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$virtKeyboardHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
VirtKeyboard create() => VirtKeyboard();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(VirtKeyState value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<VirtKeyState>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _$virtKeyboardHash() => r'1327d412bfb0dd261f3b555f353a8852b4f753e5';
|
String _$virtKeyboardHash() => r'1327d412bfb0dd261f3b555f353a8852b4f753e5';
|
||||||
|
|
||||||
/// See also [VirtKeyboard].
|
abstract class _$VirtKeyboard extends $Notifier<VirtKeyState> {
|
||||||
@ProviderFor(VirtKeyboard)
|
VirtKeyState build();
|
||||||
final virtKeyboardProvider =
|
@$mustCallSuper
|
||||||
AutoDisposeNotifierProvider<VirtKeyboard, VirtKeyState>.internal(
|
@override
|
||||||
VirtKeyboard.new,
|
void runBuild() {
|
||||||
name: r'virtKeyboardProvider',
|
final created = build();
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
final ref = this.ref as $Ref<VirtKeyState, VirtKeyState>;
|
||||||
? null
|
final element =
|
||||||
: _$virtKeyboardHash,
|
ref.element
|
||||||
dependencies: null,
|
as $ClassProviderElement<
|
||||||
allTransitiveDependencies: null,
|
AnyNotifier<VirtKeyState, VirtKeyState>,
|
||||||
);
|
VirtKeyState,
|
||||||
|
Object?,
|
||||||
typedef _$VirtKeyboard = AutoDisposeNotifier<VirtKeyState>;
|
Object?
|
||||||
// ignore_for_file: type=lint
|
>;
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
abstract class BuildData {
|
abstract class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 1231;
|
static const int build = 1291;
|
||||||
static const int script = 68;
|
static const int script = 70;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ abstract final class GithubIds {
|
|||||||
'MasedMSD',
|
'MasedMSD',
|
||||||
'GitGitro',
|
'GitGitro',
|
||||||
'Shin-suechtig',
|
'Shin-suechtig',
|
||||||
|
'GT-610'
|
||||||
};
|
};
|
||||||
|
|
||||||
static const participants = <GhId>{
|
static const participants = <GhId>{
|
||||||
@@ -126,6 +127,10 @@ abstract final class GithubIds {
|
|||||||
'cnen2018',
|
'cnen2018',
|
||||||
'xiaomeng9597',
|
'xiaomeng9597',
|
||||||
'mingzhao2019',
|
'mingzhao2019',
|
||||||
|
'HHXXYY123',
|
||||||
|
'Lancerys',
|
||||||
|
'yaziku',
|
||||||
|
'yeluosln',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:fl_lib/fl_lib.dart';
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:server_box/data/store/connection_stats.dart';
|
||||||
import 'package:server_box/data/store/container.dart';
|
import 'package:server_box/data/store/container.dart';
|
||||||
import 'package:server_box/data/store/history.dart';
|
import 'package:server_box/data/store/history.dart';
|
||||||
import 'package:server_box/data/store/private_key.dart';
|
import 'package:server_box/data/store/private_key.dart';
|
||||||
@@ -6,25 +8,37 @@ import 'package:server_box/data/store/server.dart';
|
|||||||
import 'package:server_box/data/store/setting.dart';
|
import 'package:server_box/data/store/setting.dart';
|
||||||
import 'package:server_box/data/store/snippet.dart';
|
import 'package:server_box/data/store/snippet.dart';
|
||||||
|
|
||||||
|
final GetIt getIt = GetIt.instance;
|
||||||
|
|
||||||
abstract final class Stores {
|
abstract final class Stores {
|
||||||
static final setting = SettingStore.instance;
|
static SettingStore get setting => getIt<SettingStore>();
|
||||||
static final server = ServerStore.instance;
|
static ServerStore get server => getIt<ServerStore>();
|
||||||
static final container = ContainerStore.instance;
|
static ContainerStore get container => getIt<ContainerStore>();
|
||||||
static final key = PrivateKeyStore.instance;
|
static PrivateKeyStore get key => getIt<PrivateKeyStore>();
|
||||||
static final snippet = SnippetStore.instance;
|
static SnippetStore get snippet => getIt<SnippetStore>();
|
||||||
static final history = HistoryStore.instance;
|
static HistoryStore get history => getIt<HistoryStore>();
|
||||||
|
static ConnectionStatsStore get connectionStats => getIt<ConnectionStatsStore>();
|
||||||
|
|
||||||
/// All stores that need backup
|
/// All stores that need backup
|
||||||
static final List<HiveStore> _allBackup = [
|
static List<HiveStore> get _allBackup => [
|
||||||
SettingStore.instance,
|
setting,
|
||||||
ServerStore.instance,
|
server,
|
||||||
ContainerStore.instance,
|
container,
|
||||||
PrivateKeyStore.instance,
|
key,
|
||||||
SnippetStore.instance,
|
snippet,
|
||||||
HistoryStore.instance,
|
history,
|
||||||
];
|
connectionStats,
|
||||||
|
];
|
||||||
|
|
||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
|
getIt.registerLazySingleton<SettingStore>(() => SettingStore.instance);
|
||||||
|
getIt.registerLazySingleton<ServerStore>(() => ServerStore.instance);
|
||||||
|
getIt.registerLazySingleton<ContainerStore>(() => ContainerStore.instance);
|
||||||
|
getIt.registerLazySingleton<PrivateKeyStore>(() => PrivateKeyStore.instance);
|
||||||
|
getIt.registerLazySingleton<SnippetStore>(() => SnippetStore.instance);
|
||||||
|
getIt.registerLazySingleton<HistoryStore>(() => HistoryStore.instance);
|
||||||
|
getIt.registerLazySingleton<ConnectionStatsStore>(() => ConnectionStatsStore.instance);
|
||||||
|
|
||||||
await Future.wait(_allBackup.map((store) => store.init()));
|
await Future.wait(_allBackup.map((store) => store.init()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,31 @@ abstract final class TermSessionManager {
|
|||||||
|
|
||||||
static void init() {
|
static void init() {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
MethodChans.registerHandler((id) async {
|
MethodChans.registerHandler(
|
||||||
_entries[id]?.disconnect?.call();
|
(id) async {
|
||||||
});
|
_entries[id]?.disconnect?.call();
|
||||||
|
},
|
||||||
|
() {
|
||||||
|
// Stop all connections when notification "Stop All" is pressed
|
||||||
|
stopAllConnections();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when Android notification "Stop All" button is pressed
|
||||||
|
static void stopAllConnections() {
|
||||||
|
// Disconnect all sessions
|
||||||
|
final disconnectCallbacks = _entries.values.map((e) => e.disconnect).where((cb) => cb != null).toList();
|
||||||
|
for (final disconnect in disconnectCallbacks) {
|
||||||
|
disconnect!();
|
||||||
|
}
|
||||||
|
// Clear all entries
|
||||||
|
_entries.clear();
|
||||||
|
_activeId = null;
|
||||||
|
_sync();
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a session record and push update to Android.
|
/// Add a session record and push update to Android.
|
||||||
static void add({
|
static void add({
|
||||||
required String id,
|
required String id,
|
||||||
|
|||||||
190
lib/data/store/connection_stats.dart
Normal file
190
lib/data/store/connection_stats.dart
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:server_box/data/model/server/connection_stat.dart';
|
||||||
|
|
||||||
|
class ConnectionStatsStore extends HiveStore {
|
||||||
|
ConnectionStatsStore._() : super('connection_stats');
|
||||||
|
|
||||||
|
static final instance = ConnectionStatsStore._();
|
||||||
|
|
||||||
|
// Record a connection attempt
|
||||||
|
void recordConnection(ConnectionStat stat) {
|
||||||
|
final key = '${stat.serverId}_${ShortId.generate()}';
|
||||||
|
set(key, stat);
|
||||||
|
_cleanOldRecords(stat.serverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean records older than 30 days for a specific server
|
||||||
|
void _cleanOldRecords(String serverId) {
|
||||||
|
final cutoffTime = DateTime.now().subtract(const Duration(days: 30));
|
||||||
|
final allKeys = keys().toList();
|
||||||
|
final keysToDelete = <String>[];
|
||||||
|
|
||||||
|
for (final key in allKeys) {
|
||||||
|
if (key.startsWith(serverId)) {
|
||||||
|
final parts = key.split('_');
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
final timestamp = int.tryParse(parts.last);
|
||||||
|
if (timestamp != null) {
|
||||||
|
final recordTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||||
|
if (recordTime.isBefore(cutoffTime)) {
|
||||||
|
keysToDelete.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final key in keysToDelete) {
|
||||||
|
remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection stats for a specific server
|
||||||
|
ServerConnectionStats getServerStats(String serverId, String serverName) {
|
||||||
|
final allStats = getConnectionHistory(serverId);
|
||||||
|
|
||||||
|
if (allStats.isEmpty) {
|
||||||
|
return ServerConnectionStats(
|
||||||
|
serverId: serverId,
|
||||||
|
serverName: serverName,
|
||||||
|
totalAttempts: 0,
|
||||||
|
successCount: 0,
|
||||||
|
failureCount: 0,
|
||||||
|
recentConnections: [],
|
||||||
|
successRate: 0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalAttempts = allStats.length;
|
||||||
|
final successCount = allStats.where((s) => s.result.isSuccess).length;
|
||||||
|
final failureCount = totalAttempts - successCount;
|
||||||
|
final successRate = totalAttempts > 0 ? (successCount / totalAttempts) : 0.0;
|
||||||
|
|
||||||
|
final successTimes = allStats
|
||||||
|
.where((s) => s.result.isSuccess)
|
||||||
|
.map((s) => s.timestamp)
|
||||||
|
.toList();
|
||||||
|
final failureTimes = allStats
|
||||||
|
.where((s) => !s.result.isSuccess)
|
||||||
|
.map((s) => s.timestamp)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
DateTime? lastSuccessTime;
|
||||||
|
DateTime? lastFailureTime;
|
||||||
|
|
||||||
|
if (successTimes.isNotEmpty) {
|
||||||
|
successTimes.sort((a, b) => b.compareTo(a));
|
||||||
|
lastSuccessTime = successTimes.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureTimes.isNotEmpty) {
|
||||||
|
failureTimes.sort((a, b) => b.compareTo(a));
|
||||||
|
lastFailureTime = failureTimes.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get recent connections (last 20)
|
||||||
|
final recentConnections = allStats.take(20).toList();
|
||||||
|
|
||||||
|
return ServerConnectionStats(
|
||||||
|
serverId: serverId,
|
||||||
|
serverName: serverName,
|
||||||
|
totalAttempts: totalAttempts,
|
||||||
|
successCount: successCount,
|
||||||
|
failureCount: failureCount,
|
||||||
|
lastSuccessTime: lastSuccessTime,
|
||||||
|
lastFailureTime: lastFailureTime,
|
||||||
|
recentConnections: recentConnections,
|
||||||
|
successRate: successRate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection history for a specific server
|
||||||
|
List<ConnectionStat> getConnectionHistory(String serverId) {
|
||||||
|
final allKeys = keys().where((key) => key.startsWith(serverId)).toList();
|
||||||
|
final stats = <ConnectionStat>[];
|
||||||
|
|
||||||
|
for (final key in allKeys) {
|
||||||
|
final stat = get<ConnectionStat>(
|
||||||
|
key,
|
||||||
|
fromObj: (val) {
|
||||||
|
if (val is ConnectionStat) return val;
|
||||||
|
if (val is Map<dynamic, dynamic>) {
|
||||||
|
final map = val.toStrDynMap;
|
||||||
|
if (map == null) return null;
|
||||||
|
try {
|
||||||
|
return ConnectionStat.fromJson(map as Map<String, dynamic>);
|
||||||
|
} catch (e) {
|
||||||
|
dprint('Parsing ConnectionStat from JSON', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (stat != null) {
|
||||||
|
stats.add(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by timestamp, newest first
|
||||||
|
stats.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all servers' stats
|
||||||
|
List<ServerConnectionStats> getAllServerStats() {
|
||||||
|
final serverIds = <String>{};
|
||||||
|
final serverNames = <String, String>{};
|
||||||
|
|
||||||
|
// Get all unique server IDs
|
||||||
|
for (final key in keys()) {
|
||||||
|
final parts = key.split('_');
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
final serverId = parts[0];
|
||||||
|
serverIds.add(serverId);
|
||||||
|
|
||||||
|
// Try to get server name from the stored stat
|
||||||
|
final stat = get<ConnectionStat>(
|
||||||
|
key,
|
||||||
|
fromObj: (val) {
|
||||||
|
if (val is ConnectionStat) return val;
|
||||||
|
if (val is Map<dynamic, dynamic>) {
|
||||||
|
final map = val.toStrDynMap;
|
||||||
|
if (map == null) return null;
|
||||||
|
try {
|
||||||
|
return ConnectionStat.fromJson(map as Map<String, dynamic>);
|
||||||
|
} catch (e) {
|
||||||
|
dprint('Parsing ConnectionStat from JSON', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (stat != null) {
|
||||||
|
serverNames[serverId] = stat.serverName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final allStats = <ServerConnectionStats>[];
|
||||||
|
for (final serverId in serverIds) {
|
||||||
|
final serverName = serverNames[serverId] ?? serverId;
|
||||||
|
final stats = getServerStats(serverId, serverName);
|
||||||
|
allStats.add(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all connection stats
|
||||||
|
void clearAll() {
|
||||||
|
box.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear stats for a specific server
|
||||||
|
void clearServerStats(String serverId) {
|
||||||
|
final keysToDelete = keys().where((key) => key.startsWith(serverId)).toList();
|
||||||
|
for (final key in keysToDelete) {
|
||||||
|
remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,15 +89,12 @@ class ServerStore extends HiveStore {
|
|||||||
// Replace ids in jump server settings.
|
// Replace ids in jump server settings.
|
||||||
final spi = get<Spi>(newId);
|
final spi = get<Spi>(newId);
|
||||||
if (spi != null) {
|
if (spi != null) {
|
||||||
final jumpId = spi.jumpId; // This could be an oldId.
|
final jumpChainIds = spi.jumpChainIds ?? (spi.jumpId == null ? null : [spi.jumpId!]);
|
||||||
// Check if this jumpId corresponds to a server that was also migrated.
|
if (jumpChainIds == null || jumpChainIds.isEmpty) continue;
|
||||||
if (jumpId != null && idMap.containsKey(jumpId)) {
|
|
||||||
final newJumpId = idMap[jumpId];
|
final newChain = jumpChainIds.map((e) => idMap[e] ?? e).toList();
|
||||||
if (spi.jumpId != newJumpId) {
|
final newSpi = spi.copyWith(jumpId: null, jumpChainIds: newChain);
|
||||||
final newSpi = spi.copyWith(jumpId: newJumpId);
|
update(spi, newSpi);
|
||||||
update(spi, newSpi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace ids in [Snippet]
|
// Replace ids in [Snippet]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:fl_lib/fl_lib.dart';
|
|||||||
import 'package:server_box/data/model/app/menu/server_func.dart';
|
import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||||
import 'package:server_box/data/model/app/net_view.dart';
|
import 'package:server_box/data/model/app/net_view.dart';
|
||||||
import 'package:server_box/data/model/app/server_detail_card.dart';
|
import 'package:server_box/data/model/app/server_detail_card.dart';
|
||||||
|
import 'package:server_box/data/model/app/tab.dart';
|
||||||
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||||
import 'package:server_box/data/res/default.dart';
|
import 'package:server_box/data/res/default.dart';
|
||||||
|
|
||||||
@@ -22,10 +23,7 @@ class SettingStore extends HiveStore {
|
|||||||
// late final launchPage = property('launchPage', Defaults.launchPageIdx);
|
// late final launchPage = property('launchPage', Defaults.launchPageIdx);
|
||||||
|
|
||||||
/// Disk view: amount / IO
|
/// Disk view: amount / IO
|
||||||
late final serverTabPreferDiskAmount = propertyDefault(
|
late final serverTabPreferDiskAmount = propertyDefault('serverTabPreferDiskAmount', false);
|
||||||
'serverTabPreferDiskAmount',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Bigger for bigger font size
|
/// Bigger for bigger font size
|
||||||
/// 1.0 means 100%
|
/// 1.0 means 100%
|
||||||
@@ -70,20 +68,26 @@ class SettingStore extends HiveStore {
|
|||||||
late final locale = propertyDefault('locale', '');
|
late final locale = propertyDefault('locale', '');
|
||||||
|
|
||||||
// SSH virtual key (ctrl | alt) auto turn off
|
// SSH virtual key (ctrl | alt) auto turn off
|
||||||
late final sshVirtualKeyAutoOff = propertyDefault(
|
late final sshVirtualKeyAutoOff = propertyDefault('sshVirtualKeyAutoOff', true);
|
||||||
'sshVirtualKeyAutoOff',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final editorFontSize = propertyDefault('editorFontSize', 12.5);
|
late final editorFontSize = propertyDefault('editorFontSize', 12.5);
|
||||||
|
|
||||||
|
/// Trusted SSH host key fingerprints keyed by `serverId::keyType`.
|
||||||
|
late final sshKnownHostFingerprints = propertyDefault<Map<String, String>>(
|
||||||
|
'sshKnownHostFingerprints',
|
||||||
|
const {},
|
||||||
|
fromObj: (raw) {
|
||||||
|
if (raw is Map) {
|
||||||
|
return raw.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||||||
|
}
|
||||||
|
return <String, String>{};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Editor theme
|
// Editor theme
|
||||||
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
|
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
|
||||||
|
|
||||||
late final editorDarkTheme = propertyDefault(
|
late final editorDarkTheme = propertyDefault('editorDarkTheme', Defaults.editorDarkTheme);
|
||||||
'editorDarkTheme',
|
|
||||||
Defaults.editorDarkTheme,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final fullScreen = propertyDefault('fullScreen', false);
|
late final fullScreen = propertyDefault('fullScreen', false);
|
||||||
|
|
||||||
@@ -113,29 +117,20 @@ class SettingStore extends HiveStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Only valid on iOS
|
// Only valid on iOS
|
||||||
late final autoUpdateHomeWidget = propertyDefault(
|
late final autoUpdateHomeWidget = propertyDefault('autoUpdateHomeWidget', isIOS);
|
||||||
'autoUpdateHomeWidget',
|
|
||||||
isIOS,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', true);
|
late final autoCheckAppUpdate = propertyDefault('autoCheckAppUpdate', true);
|
||||||
|
|
||||||
/// Display server tab function buttons on the bottom of each server card if [true]
|
/// Display server tab function buttons on the bottom of each server card if [true]
|
||||||
///
|
///
|
||||||
/// Otherwise, display them on the top of server detail page
|
/// Otherwise, display them on the top of server detail page
|
||||||
late final moveServerFuncs = propertyDefault(
|
late final moveServerFuncs = propertyDefault('moveOutServerTabFuncBtns', false);
|
||||||
'moveOutServerTabFuncBtns',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Whether use `rm -r` to delete directory on SFTP
|
/// Whether use `rm -r` to delete directory on SFTP
|
||||||
late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
|
late final sftpRmrDir = propertyDefault('sftpRmrDir', false);
|
||||||
|
|
||||||
/// Whether use system's primary color as the app's primary color
|
/// Whether use system's primary color as the app's primary color
|
||||||
late final useSystemPrimaryColor = propertyDefault(
|
late final useSystemPrimaryColor = propertyDefault('useSystemPrimaryColor', false);
|
||||||
'useSystemPrimaryColor',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Only valid on iOS / Android / Windows
|
/// Only valid on iOS / Android / Windows
|
||||||
late final useBioAuth = propertyDefault('useBioAuth', false);
|
late final useBioAuth = propertyDefault('useBioAuth', false);
|
||||||
@@ -151,10 +146,7 @@ class SettingStore extends HiveStore {
|
|||||||
late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
|
late final sftpOpenLastPath = propertyDefault('sftpOpenLastPath', true);
|
||||||
|
|
||||||
/// Show folders first in SFTP file browser
|
/// Show folders first in SFTP file browser
|
||||||
late final sftpShowFoldersFirst = propertyDefault(
|
late final sftpShowFoldersFirst = propertyDefault('sftpShowFoldersFirst', true);
|
||||||
'sftpShowFoldersFirst',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Show tip of suspend
|
/// Show tip of suspend
|
||||||
late final showSuspendTip = propertyDefault('showSuspendTip', true);
|
late final showSuspendTip = propertyDefault('showSuspendTip', true);
|
||||||
@@ -162,10 +154,12 @@ class SettingStore extends HiveStore {
|
|||||||
/// Whether collapse UI items by default
|
/// Whether collapse UI items by default
|
||||||
late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
|
late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
|
||||||
|
|
||||||
late final serverFuncBtns = listProperty(
|
/// Terminal AI helper configuration
|
||||||
'serverBtns',
|
late final askAiBaseUrl = propertyDefault('askAiBaseUrl', 'https://api.openai.com');
|
||||||
defaultValue: ServerFuncBtn.defaultIdxs,
|
late final askAiApiKey = propertyDefault('askAiApiKey', '');
|
||||||
);
|
late final askAiModel = propertyDefault('askAiModel', 'gpt-4o-mini');
|
||||||
|
|
||||||
|
late final serverFuncBtns = listProperty('serverBtns', defaultValue: ServerFuncBtn.defaultIdxs);
|
||||||
|
|
||||||
/// Docker is more popular than podman, set to `false` to use docker
|
/// Docker is more popular than podman, set to `false` to use docker
|
||||||
late final usePodman = propertyDefault('usePodman', false);
|
late final usePodman = propertyDefault('usePodman', false);
|
||||||
@@ -180,16 +174,10 @@ class SettingStore extends HiveStore {
|
|||||||
late final containerParseStat = propertyDefault('containerParseStat', true);
|
late final containerParseStat = propertyDefault('containerParseStat', true);
|
||||||
|
|
||||||
/// Auto refresh container status
|
/// Auto refresh container status
|
||||||
late final containerAutoRefresh = propertyDefault(
|
late final containerAutoRefresh = propertyDefault('containerAutoRefresh', true);
|
||||||
'containerAutoRefresh',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Use double column servers page on Desktop
|
/// Use double column servers page on Desktop
|
||||||
late final doubleColumnServersPage = propertyDefault(
|
late final doubleColumnServersPage = propertyDefault('doubleColumnServersPage', true);
|
||||||
'doubleColumnServersPage',
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Ignore local network device (eg: br-xxx, ovs-system...)
|
/// Ignore local network device (eg: br-xxx, ovs-system...)
|
||||||
/// when building traffic view on server tab
|
/// when building traffic view on server tab
|
||||||
@@ -244,8 +232,7 @@ class SettingStore extends HiveStore {
|
|||||||
/// Record the position and size of the window.
|
/// Record the position and size of the window.
|
||||||
late final windowState = property<WindowState>(
|
late final windowState = property<WindowState>(
|
||||||
'windowState',
|
'windowState',
|
||||||
fromObj: (raw) =>
|
fromObj: (raw) => WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
||||||
WindowState.fromJson(jsonDecode(raw as String) as Map<String, dynamic>),
|
|
||||||
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
|
toObj: (state) => state == null ? null : jsonEncode(state.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,10 +245,7 @@ class SettingStore extends HiveStore {
|
|||||||
late final sftpEditor = propertyDefault('sftpEditor', '');
|
late final sftpEditor = propertyDefault('sftpEditor', '');
|
||||||
|
|
||||||
/// Preferred terminal emulator command on desktop
|
/// Preferred terminal emulator command on desktop
|
||||||
late final desktopTerminal = propertyDefault(
|
late final desktopTerminal = propertyDefault('desktopTerminal', 'x-terminal-emulator');
|
||||||
'desktopTerminal',
|
|
||||||
'x-terminal-emulator',
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Run foreground service on Android, if the SSH terminal is running
|
/// Run foreground service on Android, if the SSH terminal is running
|
||||||
late final fgService = propertyDefault('fgService', false);
|
late final fgService = propertyDefault('fgService', false);
|
||||||
@@ -280,4 +264,14 @@ class SettingStore extends HiveStore {
|
|||||||
|
|
||||||
/// Whether to read SSH config from ~/.ssh/config on first time
|
/// Whether to read SSH config from ~/.ssh/config on first time
|
||||||
late final firstTimeReadSSHCfg = propertyDefault('firstTimeReadSSHCfg', true);
|
late final firstTimeReadSSHCfg = propertyDefault('firstTimeReadSSHCfg', true);
|
||||||
|
|
||||||
|
/// Tabs at home page
|
||||||
|
late final homeTabs = listProperty(
|
||||||
|
'homeTabs',
|
||||||
|
defaultValue: AppTab.values,
|
||||||
|
fromObj: AppTab.parseAppTabsFromObj,
|
||||||
|
toObj: (val) {
|
||||||
|
return val?.map((e) => e.name).toList() ?? [];
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,108 @@ abstract class AppLocalizations {
|
|||||||
/// **'Already in last directory.'**
|
/// **'Already in last directory.'**
|
||||||
String get alreadyLastDir;
|
String get alreadyLastDir;
|
||||||
|
|
||||||
|
/// No description provided for @askAi.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Ask AI'**
|
||||||
|
String get askAi;
|
||||||
|
|
||||||
|
/// No description provided for @askAiApiKey.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'API Key'**
|
||||||
|
String get askAiApiKey;
|
||||||
|
|
||||||
|
/// No description provided for @askAiAwaitingResponse.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Waiting for AI response...'**
|
||||||
|
String get askAiAwaitingResponse;
|
||||||
|
|
||||||
|
/// No description provided for @askAiBaseUrl.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Base URL'**
|
||||||
|
String get askAiBaseUrl;
|
||||||
|
|
||||||
|
/// No description provided for @askAiCommandInserted.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Command inserted into terminal'**
|
||||||
|
String get askAiCommandInserted;
|
||||||
|
|
||||||
|
/// No description provided for @askAiConfigMissing.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Please configure {fields} in Settings.'**
|
||||||
|
String askAiConfigMissing(Object fields);
|
||||||
|
|
||||||
|
/// No description provided for @askAiConfirmExecute.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Confirm before executing'**
|
||||||
|
String get askAiConfirmExecute;
|
||||||
|
|
||||||
|
/// No description provided for @askAiConversation.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'AI conversation'**
|
||||||
|
String get askAiConversation;
|
||||||
|
|
||||||
|
/// No description provided for @askAiDisclaimer.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'AI may be incorrect. Review carefully before applying.'**
|
||||||
|
String get askAiDisclaimer;
|
||||||
|
|
||||||
|
/// No description provided for @askAiFollowUpHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Ask a follow-up...'**
|
||||||
|
String get askAiFollowUpHint;
|
||||||
|
|
||||||
|
/// No description provided for @askAiInsertTerminal.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Insert into terminal'**
|
||||||
|
String get askAiInsertTerminal;
|
||||||
|
|
||||||
|
/// No description provided for @askAiModel.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Model'**
|
||||||
|
String get askAiModel;
|
||||||
|
|
||||||
|
/// No description provided for @askAiNoResponse.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No response'**
|
||||||
|
String get askAiNoResponse;
|
||||||
|
|
||||||
|
/// No description provided for @askAiRecommendedCommand.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'AI suggested command'**
|
||||||
|
String get askAiRecommendedCommand;
|
||||||
|
|
||||||
|
/// No description provided for @askAiSelectedContent.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Selected content'**
|
||||||
|
String get askAiSelectedContent;
|
||||||
|
|
||||||
|
/// No description provided for @askAiUsageHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Used in SSH Terminal'**
|
||||||
|
String get askAiUsageHint;
|
||||||
|
|
||||||
|
/// No description provided for @atLeastOneTab.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'At least one tab must be selected'**
|
||||||
|
String get atLeastOneTab;
|
||||||
|
|
||||||
/// No description provided for @authFailTip.
|
/// No description provided for @authFailTip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -185,6 +287,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Automatic home widget update'**
|
/// **'Automatic home widget update'**
|
||||||
String get autoUpdateHomeWidget;
|
String get autoUpdateHomeWidget;
|
||||||
|
|
||||||
|
/// No description provided for @availableTabs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Available Tabs'**
|
||||||
|
String get availableTabs;
|
||||||
|
|
||||||
/// No description provided for @backupEncrypted.
|
/// No description provided for @backupEncrypted.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -257,6 +365,36 @@ abstract class AppLocalizations {
|
|||||||
/// **'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".'**
|
/// **'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".'**
|
||||||
String get bgRunTip;
|
String get bgRunTip;
|
||||||
|
|
||||||
|
/// No description provided for @clearAllStatsContent.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Are you sure you want to clear all server connection statistics? This action cannot be undone.'**
|
||||||
|
String get clearAllStatsContent;
|
||||||
|
|
||||||
|
/// No description provided for @clearAllStatsTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Clear All Statistics'**
|
||||||
|
String get clearAllStatsTitle;
|
||||||
|
|
||||||
|
/// No description provided for @clearServerStatsContent.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
|
||||||
|
String clearServerStatsContent(Object serverName);
|
||||||
|
|
||||||
|
/// No description provided for @clearServerStatsTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Clear {serverName} Statistics'**
|
||||||
|
String clearServerStatsTitle(Object serverName);
|
||||||
|
|
||||||
|
/// No description provided for @clearThisServerStats.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Clear This Server Statistics'**
|
||||||
|
String get clearThisServerStats;
|
||||||
|
|
||||||
/// No description provided for @closeAfterSave.
|
/// No description provided for @closeAfterSave.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -281,6 +419,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'Connection'**
|
/// **'Connection'**
|
||||||
String get conn;
|
String get conn;
|
||||||
|
|
||||||
|
/// No description provided for @connectionDetails.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Connection Details'**
|
||||||
|
String get connectionDetails;
|
||||||
|
|
||||||
|
/// No description provided for @connectionStats.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Connection Statistics'**
|
||||||
|
String get connectionStats;
|
||||||
|
|
||||||
|
/// No description provided for @connectionStatsDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'View server connection success rate and history'**
|
||||||
|
String get connectionStatsDesc;
|
||||||
|
|
||||||
/// No description provided for @container.
|
/// No description provided for @container.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -371,6 +527,30 @@ abstract class AppLocalizations {
|
|||||||
/// **'Disconnected'**
|
/// **'Disconnected'**
|
||||||
String get disconnected;
|
String get disconnected;
|
||||||
|
|
||||||
|
/// No description provided for @discoverSshServers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Discover SSH Servers'**
|
||||||
|
String get discoverSshServers;
|
||||||
|
|
||||||
|
/// No description provided for @discoveryFailed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Discovery failed'**
|
||||||
|
String get discoveryFailed;
|
||||||
|
|
||||||
|
/// No description provided for @discoverySettings.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Discovery Settings'**
|
||||||
|
String get discoverySettings;
|
||||||
|
|
||||||
|
/// No description provided for @discoverySummary.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Discovery Summary'**
|
||||||
|
String get discoverySummary;
|
||||||
|
|
||||||
/// No description provided for @disk.
|
/// No description provided for @disk.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -452,12 +632,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Edit virtual keys'**
|
/// **'Edit virtual keys'**
|
||||||
String get editVirtKeys;
|
String get editVirtKeys;
|
||||||
|
|
||||||
/// No description provided for @editor.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Editor'**
|
|
||||||
String get editor;
|
|
||||||
|
|
||||||
/// No description provided for @editorHighlightTip.
|
/// No description provided for @editorHighlightTip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -470,6 +644,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Emulator'**
|
/// **'Emulator'**
|
||||||
String get emulator;
|
String get emulator;
|
||||||
|
|
||||||
|
/// No description provided for @enableMdns.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Enable mDNS'**
|
||||||
|
String get enableMdns;
|
||||||
|
|
||||||
|
/// No description provided for @enableMdnsDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Use mDNS/Bonjour to discover SSH services'**
|
||||||
|
String get enableMdnsDesc;
|
||||||
|
|
||||||
/// No description provided for @encode.
|
/// No description provided for @encode.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -524,18 +710,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'File \'{file}\' too large {size}, max {sizeMax}'**
|
/// **'File \'{file}\' too large {size}, max {sizeMax}'**
|
||||||
String fileTooLarge(Object file, Object size, Object sizeMax);
|
String fileTooLarge(Object file, Object size, Object sizeMax);
|
||||||
|
|
||||||
|
/// No description provided for @finishedAt.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Finished at'**
|
||||||
|
String get finishedAt;
|
||||||
|
|
||||||
/// No description provided for @followSystem.
|
/// No description provided for @followSystem.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Follow system'**
|
/// **'Follow system'**
|
||||||
String get followSystem;
|
String get followSystem;
|
||||||
|
|
||||||
/// No description provided for @font.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Font'**
|
|
||||||
String get font;
|
|
||||||
|
|
||||||
/// No description provided for @fontSize.
|
/// No description provided for @fontSize.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -596,6 +782,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Code highlighting'**
|
/// **'Code highlighting'**
|
||||||
String get highlight;
|
String get highlight;
|
||||||
|
|
||||||
|
/// No description provided for @homeTabs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Home Tabs'**
|
||||||
|
String get homeTabs;
|
||||||
|
|
||||||
|
/// No description provided for @homeTabsCustomizeDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Customize which tabs appear on the home page and their order'**
|
||||||
|
String get homeTabsCustomizeDesc;
|
||||||
|
|
||||||
/// No description provided for @homeWidgetUrlConfig.
|
/// No description provided for @homeWidgetUrlConfig.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -632,12 +830,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Images list'**
|
/// **'Images list'**
|
||||||
String get imagesList;
|
String get imagesList;
|
||||||
|
|
||||||
/// No description provided for @init.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Initialize'**
|
|
||||||
String get init;
|
|
||||||
|
|
||||||
/// No description provided for @inner.
|
/// No description provided for @inner.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -692,6 +884,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Key Auth'**
|
/// **'Key Auth'**
|
||||||
String get keyAuth;
|
String get keyAuth;
|
||||||
|
|
||||||
|
/// No description provided for @lastFailure.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Last Failure'**
|
||||||
|
String get lastFailure;
|
||||||
|
|
||||||
|
/// No description provided for @lastSuccess.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Last Success'**
|
||||||
|
String get lastSuccess;
|
||||||
|
|
||||||
/// No description provided for @letterCache.
|
/// No description provided for @letterCache.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -704,12 +908,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Recommended to disable, but after disabling, it will be impossible to input CJK characters.'**
|
/// **'Recommended to disable, but after disabling, it will be impossible to input CJK characters.'**
|
||||||
String get letterCacheTip;
|
String get letterCacheTip;
|
||||||
|
|
||||||
/// No description provided for @license.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'License'**
|
|
||||||
String get license;
|
|
||||||
|
|
||||||
/// No description provided for @location.
|
/// No description provided for @location.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -728,18 +926,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Made with ❤️ by {myGithub}'**
|
/// **'Made with ❤️ by {myGithub}'**
|
||||||
String madeWithLove(Object myGithub);
|
String madeWithLove(Object myGithub);
|
||||||
|
|
||||||
/// No description provided for @manual.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Manual'**
|
|
||||||
String get manual;
|
|
||||||
|
|
||||||
/// No description provided for @max.
|
/// No description provided for @max.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'max'**
|
/// **'max'**
|
||||||
String get max;
|
String get max;
|
||||||
|
|
||||||
|
/// No description provided for @maxConcurrency.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Max Concurrency'**
|
||||||
|
String get maxConcurrency;
|
||||||
|
|
||||||
/// No description provided for @maxRetryCount.
|
/// No description provided for @maxRetryCount.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -812,6 +1010,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'New container'**
|
/// **'New container'**
|
||||||
String get newContainer;
|
String get newContainer;
|
||||||
|
|
||||||
|
/// No description provided for @noConnectionStatsData.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No connection statistics data'**
|
||||||
|
String get noConnectionStatsData;
|
||||||
|
|
||||||
/// No description provided for @noLineChart.
|
/// No description provided for @noLineChart.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -938,18 +1142,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Prioritize displaying disk capacity'**
|
/// **'Prioritize displaying disk capacity'**
|
||||||
String get preferDiskAmount;
|
String get preferDiskAmount;
|
||||||
|
|
||||||
/// No description provided for @preview.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Preview'**
|
|
||||||
String get preview;
|
|
||||||
|
|
||||||
/// No description provided for @privateKey.
|
/// No description provided for @privateKey.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Private Key'**
|
/// **'Private Key'**
|
||||||
String get privateKey;
|
String get privateKey;
|
||||||
|
|
||||||
|
/// No description provided for @privateKeyNotFoundFmt.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Private key [{keyId}] not found.'**
|
||||||
|
String privateKeyNotFoundFmt(Object keyId);
|
||||||
|
|
||||||
/// No description provided for @process.
|
/// No description provided for @process.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -998,6 +1202,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Reboot'**
|
/// **'Reboot'**
|
||||||
String get reboot;
|
String get reboot;
|
||||||
|
|
||||||
|
/// No description provided for @recentConnections.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Recent Connections'**
|
||||||
|
String get recentConnections;
|
||||||
|
|
||||||
/// No description provided for @rememberPwdInMem.
|
/// No description provided for @rememberPwdInMem.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1118,6 +1328,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Server order'**
|
/// **'Server order'**
|
||||||
String get serverOrder;
|
String get serverOrder;
|
||||||
|
|
||||||
|
/// No description provided for @serverTabRequired.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Server tab cannot be removed'**
|
||||||
|
String get serverTabRequired;
|
||||||
|
|
||||||
|
/// No description provided for @servers.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'servers'**
|
||||||
|
String get servers;
|
||||||
|
|
||||||
/// No description provided for @sftpDlPrepare.
|
/// No description provided for @sftpDlPrepare.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1256,6 +1478,42 @@ abstract class AppLocalizations {
|
|||||||
/// **'Imported {count} servers from SSH config'**
|
/// **'Imported {count} servers from SSH config'**
|
||||||
String sshConfigImported(Object count);
|
String sshConfigImported(Object count);
|
||||||
|
|
||||||
|
/// No description provided for @sshHostKeyChangedDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The SSH host key changed for {serverName}. Only continue if you trust this server.'**
|
||||||
|
String sshHostKeyChangedDesc(Object serverName);
|
||||||
|
|
||||||
|
/// No description provided for @sshHostKeyFingerprintMd5Base64.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Fingerprint (MD5 base64): {fingerprint}'**
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint);
|
||||||
|
|
||||||
|
/// No description provided for @sshHostKeyFingerprintMd5Hex.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Fingerprint (MD5 hex): {fingerprint}'**
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint);
|
||||||
|
|
||||||
|
/// Label for the SSH host key type displayed in the host key verification dialog.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'SSH host key type'**
|
||||||
|
String get sshHostKeyType;
|
||||||
|
|
||||||
|
/// No description provided for @sshHostKeyNewDesc.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'A new SSH host key was received from {serverName}. Review the fingerprint before trusting.'**
|
||||||
|
String sshHostKeyNewDesc(Object serverName);
|
||||||
|
|
||||||
|
/// No description provided for @sshHostKeyStoredFingerprint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Stored fingerprint: {fingerprint}'**
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint);
|
||||||
|
|
||||||
/// No description provided for @sshConfigManualSelect.
|
/// No description provided for @sshConfigManualSelect.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1358,12 +1616,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Switch to {val}'**
|
/// **'Switch to {val}'**
|
||||||
String switchTo(Object val);
|
String switchTo(Object val);
|
||||||
|
|
||||||
/// No description provided for @sync.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Sync'**
|
|
||||||
String get sync;
|
|
||||||
|
|
||||||
/// No description provided for @syncTip.
|
/// No description provided for @syncTip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1382,6 +1634,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Tags'**
|
/// **'Tags'**
|
||||||
String get tag;
|
String get tag;
|
||||||
|
|
||||||
|
/// No description provided for @tapToStartDiscovery.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tap the search button to discover SSH servers on your network'**
|
||||||
|
String get tapToStartDiscovery;
|
||||||
|
|
||||||
/// No description provided for @temperature.
|
/// No description provided for @temperature.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1442,6 +1700,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Total'**
|
/// **'Total'**
|
||||||
String get total;
|
String get total;
|
||||||
|
|
||||||
|
/// No description provided for @totalAttempts.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Total'**
|
||||||
|
String get totalAttempts;
|
||||||
|
|
||||||
/// No description provided for @traffic.
|
/// No description provided for @traffic.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1490,12 +1754,6 @@ abstract class AppLocalizations {
|
|||||||
/// **'Server status update interval'**
|
/// **'Server status update interval'**
|
||||||
String get updateServerStatusInterval;
|
String get updateServerStatusInterval;
|
||||||
|
|
||||||
/// No description provided for @upload.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Upload'**
|
|
||||||
String get upload;
|
|
||||||
|
|
||||||
/// No description provided for @upsideDown.
|
/// No description provided for @upsideDown.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1544,6 +1802,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'View'**
|
/// **'View'**
|
||||||
String get view;
|
String get view;
|
||||||
|
|
||||||
|
/// No description provided for @viewDetails.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'View Details'**
|
||||||
|
String get viewDetails;
|
||||||
|
|
||||||
/// No description provided for @viewErr.
|
/// No description provided for @viewErr.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1621,6 +1885,54 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'**
|
/// **'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'**
|
||||||
String get writeScriptTip;
|
String get writeScriptTip;
|
||||||
|
|
||||||
|
/// No description provided for @menuSettings.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Setting'**
|
||||||
|
String get menuSettings;
|
||||||
|
|
||||||
|
/// No description provided for @menuQuit.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Quit'**
|
||||||
|
String get menuQuit;
|
||||||
|
|
||||||
|
/// No description provided for @menuNavigate.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Navigate'**
|
||||||
|
String get menuNavigate;
|
||||||
|
|
||||||
|
/// No description provided for @menuInfo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Info'**
|
||||||
|
String get menuInfo;
|
||||||
|
|
||||||
|
/// No description provided for @menuGitHubRepository.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'GitHub Repository'**
|
||||||
|
String get menuGitHubRepository;
|
||||||
|
|
||||||
|
/// No description provided for @menuWiki.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Wiki'**
|
||||||
|
String get menuWiki;
|
||||||
|
|
||||||
|
/// No description provided for @menuHelp.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Help'**
|
||||||
|
String get menuHelp;
|
||||||
|
|
||||||
|
/// No description provided for @logs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Logs'**
|
||||||
|
String get logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -28,6 +28,60 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Bereits im letzten Verzeichnis.';
|
String get alreadyLastDir => 'Bereits im letzten Verzeichnis.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'KI fragen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API-Schlüssel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Warte auf KI-Antwort...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Basis-URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Befehl ins Terminal eingefügt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Bitte konfigurieren Sie $fields in den Einstellungen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Vor Ausführung bestätigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'KI-Unterhaltung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'KI kann Fehler machen. Bitte vorsichtig verwenden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Weitere Frage stellen...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'In Terminal einfügen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Modell';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Keine Antwort';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'KI-empfohlener Befehl';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Ausgewählter Inhalt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Verwendet im SSH-Terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.';
|
'Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'Home-Widget automatisch aktualisieren';
|
String get autoUpdateHomeWidget => 'Home-Widget automatisch aktualisieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Verfügbare Tabs';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Backup ist verschlüsselt';
|
String get backupEncrypted => 'Backup ist verschlüsselt';
|
||||||
|
|
||||||
@@ -85,6 +142,26 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".';
|
'Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Sind Sie sicher, dass Sie alle Server-Verbindungsstatistiken löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Alle Statistiken löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return '$serverName Statistiken löschen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Statistiken dieses Servers löschen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Speichern und schließen';
|
String get closeAfterSave => 'Speichern und schließen';
|
||||||
|
|
||||||
@@ -98,6 +175,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Verbindung';
|
String get conn => 'Verbindung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Verbindungsdetails';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Verbindungsstatistiken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Server-Verbindungserfolgsrate und Verlauf anzeigen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Container';
|
String get container => 'Container';
|
||||||
|
|
||||||
@@ -147,6 +234,18 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Disconnected';
|
String get disconnected => 'Disconnected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'SSH-Server entdecken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Entdeckung fehlgeschlagen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Entdeckungseinstellungen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Entdeckungs-Zusammenfassung';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Festplatte';
|
String get disk => 'Festplatte';
|
||||||
|
|
||||||
@@ -199,9 +298,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Virtuelle Tasten bearbeiten';
|
String get editVirtKeys => 'Virtuelle Tasten bearbeiten';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.';
|
'Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.';
|
||||||
@@ -209,6 +305,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulator';
|
String get emulator => 'Emulator';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'mDNS aktivieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'mDNS/Bonjour verwenden, um SSH-Dienste zu entdecken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Encode';
|
String get encode => 'Encode';
|
||||||
|
|
||||||
@@ -241,10 +344,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'System verfolgen';
|
String get finishedAt => 'Beendet um';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Schriftarten';
|
String get followSystem => 'System verfolgen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Schriftgröße';
|
String get fontSize => 'Schriftgröße';
|
||||||
@@ -277,6 +380,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Code highlight';
|
String get highlight => 'Code highlight';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home-Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Home-Widget-Link konfigurieren';
|
String get homeWidgetUrlConfig => 'Home-Widget-Link konfigurieren';
|
||||||
|
|
||||||
@@ -297,9 +407,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Images';
|
String get imagesList => 'Images';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Initialisieren';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Eingebaut';
|
String get inner => 'Eingebaut';
|
||||||
|
|
||||||
@@ -329,6 +436,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Schlüsselauthentifzierung';
|
String get keyAuth => 'Schlüsselauthentifzierung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Letzter Fehler';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Letzter Erfolg';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Buchstaben-Caching';
|
String get letterCache => 'Buchstaben-Caching';
|
||||||
|
|
||||||
@@ -336,9 +449,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.';
|
'Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Lizenzen';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Standort';
|
String get location => 'Standort';
|
||||||
|
|
||||||
@@ -351,10 +461,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Handbuch';
|
String get max => 'max';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'max';
|
String get maxConcurrency => 'Maximale Gleichzeitigkeit';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Anzahl an Verbindungsversuchen';
|
String get maxRetryCount => 'Anzahl an Verbindungsversuchen';
|
||||||
@@ -395,6 +505,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Neuer Container';
|
String get newContainer => 'Neuer Container';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Keine Verbindungsstatistikdaten';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Verwenden Sie keine Liniendiagramme';
|
String get noLineChart => 'Verwenden Sie keine Liniendiagramme';
|
||||||
|
|
||||||
@@ -466,10 +579,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen';
|
String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Vorschau';
|
String get privateKey => 'Private Key';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Private Key';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Privater Schlüssel [$keyId] wurde nicht gefunden.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Prozess';
|
String get process => 'Prozess';
|
||||||
@@ -498,6 +613,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Neustart';
|
String get reboot => 'Neustart';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Kürzliche Verbindungen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Passwort im Speicher behalten';
|
String get rememberPwdInMem => 'Passwort im Speicher behalten';
|
||||||
|
|
||||||
@@ -559,6 +677,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Server-Bestellung';
|
String get serverOrder => 'Server-Bestellung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Server-Tab kann nicht entfernt werden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'Server';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Verbindung vorbereiten...';
|
String get sftpDlPrepare => 'Verbindung vorbereiten...';
|
||||||
|
|
||||||
@@ -645,6 +769,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
return '$count Server aus SSH-Konfiguration importiert';
|
return '$count Server aus SSH-Konfiguration importiert';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'Der SSH-Hostschlüssel für $serverName hat sich geändert. Fahren Sie nur fort, wenn Sie diesem Server vertrauen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Fingerabdruck (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Fingerabdruck (MD5 Hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH-Hostschlüsseltyp';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Ein neuer SSH-Hostschlüssel wurde von $serverName empfangen. Prüfen Sie den Fingerabdruck, bevor Sie vertrauen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Gespeicherter Fingerabdruck: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?';
|
'Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?';
|
||||||
@@ -709,9 +861,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
return 'Wechseln zu $val';
|
return 'Wechseln zu $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sync';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
|
'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
|
||||||
@@ -722,6 +871,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Tags';
|
String get tag => 'Tags';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Tippen Sie auf die Suche-Schaltfläche, um SSH-Server in Ihrem Netzwerk zu entdecken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Temperatur';
|
String get temperature => 'Temperatur';
|
||||||
|
|
||||||
@@ -754,6 +907,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Gesamt';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Durchflussmenge';
|
String get traffic => 'Durchflussmenge';
|
||||||
|
|
||||||
@@ -780,9 +936,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Aktualisierungsintervall des Serverstatus';
|
'Aktualisierungsintervall des Serverstatus';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Hochladen';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Upside Down';
|
String get upsideDown => 'Upside Down';
|
||||||
|
|
||||||
@@ -808,6 +961,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Ansicht';
|
String get view => 'Ansicht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Details anzeigen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Fehler anzeigen';
|
String get viewErr => 'Fehler anzeigen';
|
||||||
|
|
||||||
@@ -851,4 +1007,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.';
|
'Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Protokolle';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,60 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Already in last directory.';
|
String get alreadyLastDir => 'Already in last directory.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Ask AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API Key';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Waiting for AI response...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Base URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Command inserted into terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Please configure $fields in Settings.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Confirm before executing';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'AI conversation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'AI may be incorrect. Review carefully before applying.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Ask a follow-up...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Insert into terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'No response';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'AI suggested command';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Selected content';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Used in SSH Terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'At least one tab must be selected';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Authentication failed, please check whether credentials are correct';
|
'Authentication failed, please check whether credentials are correct';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'Automatic home widget update';
|
String get autoUpdateHomeWidget => 'Automatic home widget update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Available Tabs';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Backup is encrypted';
|
String get backupEncrypted => 'Backup is encrypted';
|
||||||
|
|
||||||
@@ -84,6 +141,26 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".';
|
'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Are you sure you want to clear all server connection statistics? This action cannot be undone.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Clear All Statistics';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Are you sure you want to clear connection statistics for server \"$serverName\"? This action cannot be undone.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Clear $serverName Statistics';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Clear This Server Statistics';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Save and close';
|
String get closeAfterSave => 'Save and close';
|
||||||
|
|
||||||
@@ -97,6 +174,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Connection';
|
String get conn => 'Connection';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Connection Details';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Connection Statistics';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'View server connection success rate and history';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Container';
|
String get container => 'Container';
|
||||||
|
|
||||||
@@ -146,6 +233,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Disconnected';
|
String get disconnected => 'Disconnected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Discover SSH Servers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Discovery failed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Discovery Settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Discovery Summary';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disk';
|
String get disk => 'Disk';
|
||||||
|
|
||||||
@@ -198,9 +297,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Edit virtual keys';
|
String get editVirtKeys => 'Edit virtual keys';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'The current code highlighting performance is not ideal and can be optionally turned off to improve.';
|
'The current code highlighting performance is not ideal and can be optionally turned off to improve.';
|
||||||
@@ -208,6 +304,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulator';
|
String get emulator => 'Emulator';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Enable mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => 'Use mDNS/Bonjour to discover SSH services';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Encode';
|
String get encode => 'Encode';
|
||||||
|
|
||||||
@@ -240,10 +342,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Follow system';
|
String get finishedAt => 'Finished at';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Font';
|
String get followSystem => 'Follow system';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Font size';
|
String get fontSize => 'Font size';
|
||||||
@@ -276,6 +378,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Code highlighting';
|
String get highlight => 'Code highlighting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Customize which tabs appear on the home page and their order';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Config home widget url';
|
String get homeWidgetUrlConfig => 'Config home widget url';
|
||||||
|
|
||||||
@@ -296,9 +405,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Images list';
|
String get imagesList => 'Images list';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Initialize';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Inner';
|
String get inner => 'Inner';
|
||||||
|
|
||||||
@@ -328,6 +434,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Key Auth';
|
String get keyAuth => 'Key Auth';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Last Failure';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Last Success';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Letter caching';
|
String get letterCache => 'Letter caching';
|
||||||
|
|
||||||
@@ -335,9 +447,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Recommended to disable, but after disabling, it will be impossible to input CJK characters.';
|
'Recommended to disable, but after disabling, it will be impossible to input CJK characters.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'License';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Location';
|
String get location => 'Location';
|
||||||
|
|
||||||
@@ -350,10 +459,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manual';
|
String get max => 'max';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'max';
|
String get maxConcurrency => 'Max Concurrency';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Number of server reconnections';
|
String get maxRetryCount => 'Number of server reconnections';
|
||||||
@@ -393,6 +502,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'New container';
|
String get newContainer => 'New container';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'No connection statistics data';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Do not use line charts';
|
String get noLineChart => 'Do not use line charts';
|
||||||
|
|
||||||
@@ -464,10 +576,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Prioritize displaying disk capacity';
|
String get preferDiskAmount => 'Prioritize displaying disk capacity';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Preview';
|
String get privateKey => 'Private Key';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Private Key';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Private key [$keyId] not found.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Process';
|
String get process => 'Process';
|
||||||
@@ -496,6 +610,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Reboot';
|
String get reboot => 'Reboot';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Recent Connections';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Remember password in memory';
|
String get rememberPwdInMem => 'Remember password in memory';
|
||||||
|
|
||||||
@@ -556,6 +673,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Server order';
|
String get serverOrder => 'Server order';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Server tab cannot be removed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'servers';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Preparing to connect...';
|
String get sftpDlPrepare => 'Preparing to connect...';
|
||||||
|
|
||||||
@@ -640,6 +763,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
return 'Imported $count servers from SSH config';
|
return 'Imported $count servers from SSH config';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'The SSH host key changed for $serverName. Only continue if you trust this server.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Fingerprint (MD5 base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Fingerprint (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH host key type';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'A new SSH host key was received from $serverName. Review the fingerprint before trusting.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Stored fingerprint: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Would you like to select the SSH config file manually?';
|
'Would you like to select the SSH config file manually?';
|
||||||
@@ -702,9 +853,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
return 'Switch to $val';
|
return 'Switch to $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sync';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'A restart may be required for some changes to take effect.';
|
'A restart may be required for some changes to take effect.';
|
||||||
@@ -715,6 +863,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Tags';
|
String get tag => 'Tags';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Tap the search button to discover SSH servers on your network';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Temperature';
|
String get temperature => 'Temperature';
|
||||||
|
|
||||||
@@ -747,6 +899,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Traffic';
|
String get traffic => 'Traffic';
|
||||||
|
|
||||||
@@ -772,9 +927,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => 'Server status update interval';
|
String get updateServerStatusInterval => 'Server status update interval';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Upload';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Upside Down';
|
String get upsideDown => 'Upside Down';
|
||||||
|
|
||||||
@@ -800,6 +952,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'View';
|
String get view => 'View';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'View Details';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'See error';
|
String get viewErr => 'See error';
|
||||||
|
|
||||||
@@ -843,4 +998,28 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.';
|
'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Logs';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,60 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Ya estás en el directorio superior';
|
String get alreadyLastDir => 'Ya estás en el directorio superior';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Preguntar a la IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Clave API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Esperando la respuesta de la IA...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'URL base';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Comando insertado en el terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Configura $fields en Ajustes.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Confirmar antes de ejecutar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Conversación con la IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'La IA puede equivocarse. Úsala con precaución.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Haz una pregunta adicional...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Insertar en el terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Modelo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Sin respuesta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Comando sugerido por la IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Contenido seleccionado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Usado en el terminal SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.';
|
'La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get autoUpdateHomeWidget =>
|
String get autoUpdateHomeWidget =>
|
||||||
'Actualizar automáticamente el widget del escritorio';
|
'Actualizar automáticamente el widget del escritorio';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Pestañas disponibles';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'El respaldo está encriptado';
|
String get backupEncrypted => 'El respaldo está encriptado';
|
||||||
|
|
||||||
@@ -85,6 +142,26 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.';
|
'Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'¿Estás seguro de que quieres limpiar todas las estadísticas de conexión del servidor? Esta acción no se puede deshacer.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Limpiar todas las estadísticas';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return '¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"$serverName\"? Esta acción no se puede deshacer.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Limpiar estadísticas de $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Limpiar estadísticas de este servidor';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Guardar y cerrar';
|
String get closeAfterSave => 'Guardar y cerrar';
|
||||||
|
|
||||||
@@ -98,6 +175,16 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Conectar';
|
String get conn => 'Conectar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Detalles de conexión';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Estadísticas de conexión';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Ver la tasa de éxito de conexión del servidor e historial';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Contenedor';
|
String get container => 'Contenedor';
|
||||||
|
|
||||||
@@ -147,6 +234,18 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Desconectado';
|
String get disconnected => 'Desconectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Descubrir servidores SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Falló el descubrimiento';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Configuración de descubrimiento';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Resumen del descubrimiento';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disco';
|
String get disk => 'Disco';
|
||||||
|
|
||||||
@@ -199,9 +298,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Editar teclas virtuales';
|
String get editVirtKeys => 'Editar teclas virtuales';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.';
|
'El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.';
|
||||||
@@ -209,6 +305,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulador';
|
String get emulator => 'Emulador';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Habilitar mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => 'Usar mDNS/Bonjour para descubrir servicios SSH';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Codificar';
|
String get encode => 'Codificar';
|
||||||
|
|
||||||
@@ -241,10 +343,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Seguir al sistema';
|
String get finishedAt => 'Terminado en';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Fuente';
|
String get followSystem => 'Seguir al sistema';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Tamaño de fuente';
|
String get fontSize => 'Tamaño de fuente';
|
||||||
@@ -277,6 +379,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Resaltar código';
|
String get highlight => 'Resaltar código';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Pestañas de inicio';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personaliza qué pestañas aparecen en la página de inicio y su orden';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Configuración de URL del widget de inicio';
|
String get homeWidgetUrlConfig => 'Configuración de URL del widget de inicio';
|
||||||
|
|
||||||
@@ -297,9 +406,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Lista de imágenes';
|
String get imagesList => 'Lista de imágenes';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Inicializar';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Interno';
|
String get inner => 'Interno';
|
||||||
|
|
||||||
@@ -329,6 +435,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Autenticación con llave';
|
String get keyAuth => 'Autenticación con llave';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Último fallo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Último éxito';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Caché de letras';
|
String get letterCache => 'Caché de letras';
|
||||||
|
|
||||||
@@ -336,9 +448,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.';
|
'Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Licencia de código abierto';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Ubicación';
|
String get location => 'Ubicación';
|
||||||
|
|
||||||
@@ -351,10 +460,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manual';
|
String get max => 'Máximo';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'Máximo';
|
String get maxConcurrency => 'Concurrencia máxima';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount =>
|
String get maxRetryCount =>
|
||||||
@@ -395,6 +504,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Crear contenedor nuevo';
|
String get newContainer => 'Crear contenedor nuevo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData =>
|
||||||
|
'No hay datos de estadísticas de conexión';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'No utilice gráficos de líneas';
|
String get noLineChart => 'No utilice gráficos de líneas';
|
||||||
|
|
||||||
@@ -468,10 +581,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
'Priorizar la visualización de la capacidad del disco';
|
'Priorizar la visualización de la capacidad del disco';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Vista previa';
|
String get privateKey => 'Llave privada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Llave privada';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'No se encontró la clave privada [$keyId].';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Proceso';
|
String get process => 'Proceso';
|
||||||
@@ -500,6 +615,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Reiniciar';
|
String get reboot => 'Reiniciar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Conexiones recientes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Recordar contraseña en la memoria';
|
String get rememberPwdInMem => 'Recordar contraseña en la memoria';
|
||||||
|
|
||||||
@@ -562,6 +680,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Orden del servidor';
|
String get serverOrder => 'Orden del servidor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired =>
|
||||||
|
'La pestaña del servidor no se puede eliminar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'servidores';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Preparando para conectar al servidor...';
|
String get sftpDlPrepare => 'Preparando para conectar al servidor...';
|
||||||
|
|
||||||
@@ -647,6 +772,34 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
return 'Se importaron $count servidores desde la configuración SSH';
|
return 'Se importaron $count servidores desde la configuración SSH';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'La clave de host SSH de $serverName ha cambiado. Continúa solo si confías en este servidor.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Huella (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Huella (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Tipo de clave de host SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Se recibió una nueva clave de host SSH de $serverName. Revisa la huella antes de confiar.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Huella almacenada: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'¿Te gustaría seleccionar manualmente el archivo de configuración SSH?';
|
'¿Te gustaría seleccionar manualmente el archivo de configuración SSH?';
|
||||||
@@ -710,9 +863,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
return 'Cambiar a $val';
|
return 'Cambiar a $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sincronizar';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Puede que necesites reiniciar para que algunos cambios tengan efecto.';
|
'Puede que necesites reiniciar para que algunos cambios tengan efecto.';
|
||||||
@@ -723,6 +873,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Etiqueta';
|
String get tag => 'Etiqueta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Toca el botón de búsqueda para descubrir servidores SSH en tu red';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Temperatura';
|
String get temperature => 'Temperatura';
|
||||||
|
|
||||||
@@ -755,6 +909,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Tráfico';
|
String get traffic => 'Tráfico';
|
||||||
|
|
||||||
@@ -781,9 +938,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Intervalo de actualización del estado del servidor';
|
'Intervalo de actualización del estado del servidor';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Subir';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Invertir arriba por abajo';
|
String get upsideDown => 'Invertir arriba por abajo';
|
||||||
|
|
||||||
@@ -809,6 +963,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Vista';
|
String get view => 'Vista';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Ver detalles';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Ver error';
|
String get viewErr => 'Ver error';
|
||||||
|
|
||||||
@@ -852,4 +1009,28 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.';
|
'Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Registros';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,60 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Déjà dans le dernier répertoire.';
|
String get alreadyLastDir => 'Déjà dans le dernier répertoire.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Demander à l\'IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Clé API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'En attente de la réponse de l\'IA...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'URL de base';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Commande insérée dans le terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Veuillez configurer $fields dans les paramètres.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Confirmer avant d\'exécuter';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Conversation avec l\'IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'L\'IA peut se tromper. Utilisez-la avec prudence.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Poser une question supplémentaire...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Insérer dans le terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Modèle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Aucune réponse';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Commande suggérée par l\'IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Contenu sélectionné';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Utilisé dans le terminal SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Au moins un onglet doit être sélectionné';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Échec de l\'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.';
|
'Échec de l\'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get autoUpdateHomeWidget =>
|
String get autoUpdateHomeWidget =>
|
||||||
'Mise à jour automatique du widget d\'accueil';
|
'Mise à jour automatique du widget d\'accueil';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Onglets disponibles';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'La sauvegarde est chiffrée';
|
String get backupEncrypted => 'La sauvegarde est chiffrée';
|
||||||
|
|
||||||
@@ -85,6 +142,26 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Cette option signifie seulement que le programme essaiera de s\'exécuter en arrière-plan, que cela soit possible dépend de l\'autorisation activée ou non. Pour Android natif, veuillez désactiver l\'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d\'économie d\'énergie en « Illimité ».';
|
'Cette option signifie seulement que le programme essaiera de s\'exécuter en arrière-plan, que cela soit possible dépend de l\'autorisation activée ou non. Pour Android natif, veuillez désactiver l\'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d\'économie d\'énergie en « Illimité ».';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Êtes-vous sûr de vouloir effacer toutes les statistiques de connexion des serveurs ? Cette action ne peut pas être annulée.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Effacer toutes les statistiques';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"$serverName\" ? Cette action ne peut pas être annulée.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Effacer les statistiques de $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Effacer les statistiques de ce serveur';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Enregistrer et fermer';
|
String get closeAfterSave => 'Enregistrer et fermer';
|
||||||
|
|
||||||
@@ -98,6 +175,16 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Connexion';
|
String get conn => 'Connexion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Détails de connexion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Statistiques de connexion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Voir le taux de réussite de connexion du serveur et l\'historique';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Conteneur';
|
String get container => 'Conteneur';
|
||||||
|
|
||||||
@@ -147,6 +234,18 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Déconnecté';
|
String get disconnected => 'Déconnecté';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Découvrir les serveurs SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Échec de la découverte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Paramètres de découverte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Résumé de la découverte';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disque';
|
String get disk => 'Disque';
|
||||||
|
|
||||||
@@ -199,9 +298,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Modifier les touches virtuelles';
|
String get editVirtKeys => 'Modifier les touches virtuelles';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Éditeur';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s\'améliorer.';
|
'La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s\'améliorer.';
|
||||||
@@ -209,6 +305,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Émulateur';
|
String get emulator => 'Émulateur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Activer mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'Utiliser mDNS/Bonjour pour découvrir les services SSH';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Encoder';
|
String get encode => 'Encoder';
|
||||||
|
|
||||||
@@ -241,10 +344,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Suivre le système';
|
String get finishedAt => 'Terminé à';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Police';
|
String get followSystem => 'Suivre le système';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Taille de la police';
|
String get fontSize => 'Taille de la police';
|
||||||
@@ -277,6 +380,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Mise en surbrillance du code';
|
String get highlight => 'Mise en surbrillance du code';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Onglets d\'accueil';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personnalisez les onglets qui apparaissent sur la page d\'accueil et leur ordre';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Configurer l\'URL du widget d\'accueil';
|
String get homeWidgetUrlConfig => 'Configurer l\'URL du widget d\'accueil';
|
||||||
|
|
||||||
@@ -297,9 +407,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Liste des images';
|
String get imagesList => 'Liste des images';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Initialiser';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Interne';
|
String get inner => 'Interne';
|
||||||
|
|
||||||
@@ -329,6 +436,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Authentification par clé';
|
String get keyAuth => 'Authentification par clé';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Dernier échec';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Dernier succès';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Mise en cache des lettres';
|
String get letterCache => 'Mise en cache des lettres';
|
||||||
|
|
||||||
@@ -336,9 +449,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.';
|
'Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Licence';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Emplacement';
|
String get location => 'Emplacement';
|
||||||
|
|
||||||
@@ -351,10 +461,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manuel';
|
String get max => 'max';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'max';
|
String get maxConcurrency => 'Concurrence maximale';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Nombre de reconnexions au serveur';
|
String get maxRetryCount => 'Nombre de reconnexions au serveur';
|
||||||
@@ -394,6 +504,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Nouveau conteneur';
|
String get newContainer => 'Nouveau conteneur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData =>
|
||||||
|
'Aucune donnée de statistiques de connexion';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Ne pas utiliser de graphiques linéaires';
|
String get noLineChart => 'Ne pas utiliser de graphiques linéaires';
|
||||||
|
|
||||||
@@ -469,10 +583,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
'Prioriser l’affichage de la capacité du disque';
|
'Prioriser l’affichage de la capacité du disque';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Aperçu';
|
String get privateKey => 'Clé privée';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Clé privée';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Clé privée [$keyId] introuvable.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Processus';
|
String get process => 'Processus';
|
||||||
@@ -501,6 +617,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Redémarrer';
|
String get reboot => 'Redémarrer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Connexions récentes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Mémoriser le mot de passe en mémoire';
|
String get rememberPwdInMem => 'Mémoriser le mot de passe en mémoire';
|
||||||
|
|
||||||
@@ -563,6 +682,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Ordre du serveur';
|
String get serverOrder => 'Ordre du serveur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'L\'onglet serveur ne peut pas être supprimé';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'serveurs';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Préparation de la connexion...';
|
String get sftpDlPrepare => 'Préparation de la connexion...';
|
||||||
|
|
||||||
@@ -649,6 +774,34 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
return '$count serveurs importés depuis la configuration SSH';
|
return '$count serveurs importés depuis la configuration SSH';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'La clé d\'hôte SSH de $serverName a changé. Ne continuez que si vous faites confiance à ce serveur.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Empreinte (MD5 Base64) : $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Empreinte (MD5 hex) : $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Type de clé d\'hôte SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Une nouvelle clé d\'hôte SSH a été reçue de $serverName. Vérifiez l\'empreinte avant de faire confiance.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Empreinte enregistrée : $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?';
|
'Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?';
|
||||||
@@ -713,9 +866,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
return 'Passer à $val';
|
return 'Passer à $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sync';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Un redémarrage peut être nécessaire pour que certains changements prennent effet.';
|
'Un redémarrage peut être nécessaire pour que certains changements prennent effet.';
|
||||||
@@ -726,6 +876,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Étiquettes';
|
String get tag => 'Étiquettes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Appuyez sur le bouton de recherche pour découvrir les serveurs SSH sur votre réseau';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Température';
|
String get temperature => 'Température';
|
||||||
|
|
||||||
@@ -758,6 +912,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Trafic';
|
String get traffic => 'Trafic';
|
||||||
|
|
||||||
@@ -784,9 +941,6 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Intervalle de mise à jour de l\'état du serveur';
|
'Intervalle de mise à jour de l\'état du serveur';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Télécharger';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'À l\'envers';
|
String get upsideDown => 'À l\'envers';
|
||||||
|
|
||||||
@@ -812,6 +966,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Vue';
|
String get view => 'Vue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Voir les détails';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Voir erreur';
|
String get viewErr => 'Voir erreur';
|
||||||
|
|
||||||
@@ -855,4 +1012,28 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l\'état du système. Vous pouvez examiner le contenu du script.';
|
'Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l\'état du système. Vous pouvez examiner le contenu du script.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Journaux';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,59 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Sudah di direktori terakhir.';
|
String get alreadyLastDir => 'Sudah di direktori terakhir.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Tanya AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Kunci API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Menunggu respons AI...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'URL dasar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Perintah dimasukkan ke terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Harap konfigurasikan $fields di Pengaturan.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Konfirmasi sebelum menjalankan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Percakapan AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'AI bisa saja salah. Gunakan dengan hati-hati.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Ajukan pertanyaan lanjutan...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Masukkan ke terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Tidak ada respons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Perintah yang disarankan AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Konten yang dipilih';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Digunakan di Terminal SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Setidaknya satu tab harus dipilih';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.';
|
'Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.';
|
||||||
@@ -45,6 +98,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'Widget Rumah Pembaruan Otomatis';
|
String get autoUpdateHomeWidget => 'Widget Rumah Pembaruan Otomatis';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Tab Tersedia';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Cadangan telah dienkripsi';
|
String get backupEncrypted => 'Cadangan telah dienkripsi';
|
||||||
|
|
||||||
@@ -84,6 +140,26 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".';
|
'Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Apakah Anda yakin ingin menghapus semua statistik koneksi server? Tindakan ini tidak dapat dibatalkan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Hapus Semua Statistik';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"$serverName\"? Tindakan ini tidak dapat dibatalkan.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Hapus Statistik $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Hapus Statistik Server Ini';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Simpan dan tutup';
|
String get closeAfterSave => 'Simpan dan tutup';
|
||||||
|
|
||||||
@@ -97,6 +173,16 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Koneksi';
|
String get conn => 'Koneksi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Detail Koneksi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Statistik Koneksi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Lihat tingkat keberhasilan koneksi server dan riwayat';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Wadah';
|
String get container => 'Wadah';
|
||||||
|
|
||||||
@@ -146,6 +232,18 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Terputus';
|
String get disconnected => 'Terputus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Temukan Server SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Penemuan gagal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Pengaturan Penemuan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Ringkasan Penemuan';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disk';
|
String get disk => 'Disk';
|
||||||
|
|
||||||
@@ -198,9 +296,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Edit kunci virtual';
|
String get editVirtKeys => 'Edit kunci virtual';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.';
|
'Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.';
|
||||||
@@ -208,6 +303,13 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulator';
|
String get emulator => 'Emulator';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Aktifkan mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'Gunakan mDNS/Bonjour untuk menemukan layanan SSH';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Menyandi';
|
String get encode => 'Menyandi';
|
||||||
|
|
||||||
@@ -240,10 +342,10 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Ikuti sistem';
|
String get finishedAt => 'Selesai pada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Font';
|
String get followSystem => 'Ikuti sistem';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Ukuran huruf';
|
String get fontSize => 'Ukuran huruf';
|
||||||
@@ -276,6 +378,13 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Sorotan kode';
|
String get highlight => 'Sorotan kode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Tab Beranda';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Sesuaikan tab mana yang muncul di halaman beranda dan urutannya';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Konfigurasi URL Widget Rumah';
|
String get homeWidgetUrlConfig => 'Konfigurasi URL Widget Rumah';
|
||||||
|
|
||||||
@@ -296,9 +405,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Daftar gambar';
|
String get imagesList => 'Daftar gambar';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Menginisialisasi';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Batin';
|
String get inner => 'Batin';
|
||||||
|
|
||||||
@@ -328,6 +434,12 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Auth kunci';
|
String get keyAuth => 'Auth kunci';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Gagal Terakhir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Sukses Terakhir';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Caching huruf';
|
String get letterCache => 'Caching huruf';
|
||||||
|
|
||||||
@@ -335,9 +447,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.';
|
'Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Lisensi';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Lokasi';
|
String get location => 'Lokasi';
|
||||||
|
|
||||||
@@ -350,10 +459,10 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manual';
|
String get max => 'Max';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'Max';
|
String get maxConcurrency => 'Konkurensi Maksimum';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Jumlah penyambungan kembali server';
|
String get maxRetryCount => 'Jumlah penyambungan kembali server';
|
||||||
@@ -393,6 +502,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Wadah baru';
|
String get newContainer => 'Wadah baru';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Tidak ada data statistik koneksi';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Jangan gunakan grafik garis';
|
String get noLineChart => 'Jangan gunakan grafik garis';
|
||||||
|
|
||||||
@@ -464,10 +576,12 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Prioritaskan tampilan kapasitas disk';
|
String get preferDiskAmount => 'Prioritaskan tampilan kapasitas disk';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Pratinjau';
|
String get privateKey => 'Kunci Pribadi';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Kunci Pribadi';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Kunci privat [$keyId] tidak ditemukan.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Proses';
|
String get process => 'Proses';
|
||||||
@@ -496,6 +610,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Reboot';
|
String get reboot => 'Reboot';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Koneksi Terkini';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Ingat kata sandi di dalam memori';
|
String get rememberPwdInMem => 'Ingat kata sandi di dalam memori';
|
||||||
|
|
||||||
@@ -557,6 +674,12 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Pesanan server';
|
String get serverOrder => 'Pesanan server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Tab server tidak dapat dihapus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'server';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Bersiap untuk terhubung ...';
|
String get sftpDlPrepare => 'Bersiap untuk terhubung ...';
|
||||||
|
|
||||||
@@ -641,6 +764,34 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
return 'Berhasil mengimpor $count server dari konfigurasi SSH';
|
return 'Berhasil mengimpor $count server dari konfigurasi SSH';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'Kunci host SSH untuk $serverName telah berubah. Lanjutkan hanya jika Anda mempercayai server ini.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Sidik jari (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Sidik jari (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Jenis kunci host SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Kunci host SSH baru diterima dari $serverName. Periksa sidik jarinya sebelum mempercayai.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Sidik jari tersimpan: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Apakah Anda ingin memilih file konfigurasi SSH secara manual?';
|
'Apakah Anda ingin memilih file konfigurasi SSH secara manual?';
|
||||||
@@ -703,9 +854,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
return 'Beralih ke $val';
|
return 'Beralih ke $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sinkronisasi';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.';
|
'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.';
|
||||||
@@ -716,6 +864,10 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Tag';
|
String get tag => 'Tag';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Tekan tombol pencarian untuk menemukan server SSH di jaringan Anda';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Suhu';
|
String get temperature => 'Suhu';
|
||||||
|
|
||||||
@@ -748,6 +900,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Lalu lintas';
|
String get traffic => 'Lalu lintas';
|
||||||
|
|
||||||
@@ -773,9 +928,6 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => 'Interval Pembaruan Status Server';
|
String get updateServerStatusInterval => 'Interval Pembaruan Status Server';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Mengunggah';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Terbalik';
|
String get upsideDown => 'Terbalik';
|
||||||
|
|
||||||
@@ -801,6 +953,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Tampilan';
|
String get view => 'Tampilan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Lihat Detail';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Lihat kesalahan';
|
String get viewErr => 'Lihat kesalahan';
|
||||||
|
|
||||||
@@ -843,4 +998,28 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.';
|
'Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Log';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,59 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'すでに最上位のディレクトリです';
|
String get alreadyLastDir => 'すでに最上位のディレクトリです';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'AI に質問';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API キー';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'AI の応答を待機中...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'ベース URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'コマンドをターミナルに挿入しました';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return '設定で $fields を構成してください。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => '実行前に確認';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'AI 会話';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'AI が誤る可能性があります。注意してご利用ください。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => '追質問をする...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'ターミナルに挿入';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'モデル';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => '応答なし';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'AI 推奨コマンド';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => '選択した内容';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'SSH ターミナルで使用';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip => '認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。';
|
String get authFailTip => '認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。';
|
||||||
|
|
||||||
@@ -42,6 +95,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新';
|
String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '利用可能なタブ';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'バックアップは暗号化されています';
|
String get backupEncrypted => 'バックアップは暗号化されています';
|
||||||
|
|
||||||
@@ -80,6 +136,25 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。';
|
'このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent => 'すべてのサーバー接続統計を削除してもよろしいですか?この操作は元に戻せません。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'すべての統計をクリア';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'サーバー\"$serverName\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return '$serverNameの統計をクリア';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'このサーバーの統計をクリア';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => '保存して閉じる';
|
String get closeAfterSave => '保存して閉じる';
|
||||||
|
|
||||||
@@ -92,6 +167,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => '接続';
|
String get conn => '接続';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => '接続の詳細';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => '接続統計';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc => 'サーバー接続成功率と履歴を表示';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'コンテナ';
|
String get container => 'コンテナ';
|
||||||
|
|
||||||
@@ -139,6 +223,18 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => '接続が切断されました';
|
String get disconnected => '接続が切断されました';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'SSHサーバーの発見';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => '発見に失敗';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => '発見設定';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => '発見の概要';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'ディスク';
|
String get disk => 'ディスク';
|
||||||
|
|
||||||
@@ -191,9 +287,6 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => '仮想キーを編集';
|
String get editVirtKeys => '仮想キーを編集';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'エディター';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。';
|
'現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。';
|
||||||
@@ -201,6 +294,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'エミュレーター';
|
String get emulator => 'エミュレーター';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'mDNSを有効化';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => 'mDNS/BonjourでSSHサービスを発見';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'エンコード';
|
String get encode => 'エンコード';
|
||||||
|
|
||||||
@@ -233,10 +332,10 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'システムに従う';
|
String get finishedAt => '完了時刻';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'フォント';
|
String get followSystem => 'システムに従う';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'フォントサイズ';
|
String get fontSize => 'フォントサイズ';
|
||||||
@@ -269,6 +368,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'コードハイライト';
|
String get highlight => 'コードハイライト';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'ホームタブ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => 'ホームページに表示するタブとその順序をカスタマイズします';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'ホームウィジェットURL設定';
|
String get homeWidgetUrlConfig => 'ホームウィジェットURL設定';
|
||||||
|
|
||||||
@@ -289,9 +394,6 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'イメージリスト';
|
String get imagesList => 'イメージリスト';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => '初期化する';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => '内蔵';
|
String get inner => '内蔵';
|
||||||
|
|
||||||
@@ -320,15 +422,18 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'キー認証';
|
String get keyAuth => 'キー認証';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => '最後の失敗';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => '最後の成功';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => '文字キャッシング';
|
String get letterCache => '文字キャッシング';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCacheTip => '無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。';
|
String get letterCacheTip => '無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'オープンソースライセンス';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => '場所';
|
String get location => '場所';
|
||||||
|
|
||||||
@@ -341,10 +446,10 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'マニュアル';
|
String get max => '最大';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => '最大';
|
String get maxConcurrency => '最大同時実行数';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'サーバーの再接続試行回数';
|
String get maxRetryCount => 'サーバーの再接続試行回数';
|
||||||
@@ -384,6 +489,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => '新しいコンテナを作成';
|
String get newContainer => '新しいコンテナを作成';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => '接続統計データがありません';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => '折れ線グラフを使用しない';
|
String get noLineChart => '折れ線グラフを使用しない';
|
||||||
|
|
||||||
@@ -450,10 +558,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'ディスク容量を優先的に表示';
|
String get preferDiskAmount => 'ディスク容量を優先的に表示';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'プレビュー';
|
String get privateKey => '秘密鍵';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => '秘密鍵';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return '秘密鍵 [$keyId] が見つかりません。';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'プロセス';
|
String get process => 'プロセス';
|
||||||
@@ -481,6 +591,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => '再起動';
|
String get reboot => '再起動';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => '最近の接続';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'メモリにパスワードを記憶する';
|
String get rememberPwdInMem => 'メモリにパスワードを記憶する';
|
||||||
|
|
||||||
@@ -541,6 +654,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'サーバー順序';
|
String get serverOrder => 'サーバー順序';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'サーバータブは削除できません';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'サーバー';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'サーバーへの接続を準備中...';
|
String get sftpDlPrepare => 'サーバーへの接続を準備中...';
|
||||||
|
|
||||||
@@ -623,6 +742,34 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
return 'SSH設定から$count個のサーバーをインポートしました';
|
return 'SSH設定から$count個のサーバーをインポートしました';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return '$serverName の SSH ホスト鍵が変更されました。このサーバーを信頼できる場合のみ続行してください。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'フィンガープリント (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'フィンガープリント (MD5 16進): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH ホストキーの種類';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return '$serverName から新しい SSH ホスト鍵を受信しました。信頼する前にフィンガープリントを確認してください。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return '保存済みフィンガープリント: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect => 'SSH設定ファイルを手動で選択しますか?';
|
String get sshConfigManualSelect => 'SSH設定ファイルを手動で選択しますか?';
|
||||||
|
|
||||||
@@ -681,9 +828,6 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
return '$valに切り替える';
|
return '$valに切り替える';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => '同期する';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '再起動が必要な場合があります。一部の変更はその後に有効になります。';
|
String get syncTip => '再起動が必要な場合があります。一部の変更はその後に有効になります。';
|
||||||
|
|
||||||
@@ -693,6 +837,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'タグ';
|
String get tag => 'タグ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery => '検索ボタンをタップしてネットワーク上のSSHサーバーを発見';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => '温度';
|
String get temperature => '温度';
|
||||||
|
|
||||||
@@ -725,6 +872,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => '合計';
|
String get total => '合計';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => '総計';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'トラフィック';
|
String get traffic => 'トラフィック';
|
||||||
|
|
||||||
@@ -750,9 +900,6 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => 'サーバー状態の更新間隔';
|
String get updateServerStatusInterval => 'サーバー状態の更新間隔';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'アップロード';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => '上下逆転';
|
String get upsideDown => '上下逆転';
|
||||||
|
|
||||||
@@ -777,6 +924,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'ビュー';
|
String get view => 'ビュー';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => '詳細を表示';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'エラーを表示';
|
String get viewErr => 'エラーを表示';
|
||||||
|
|
||||||
@@ -817,5 +967,29 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。';
|
'サーバーへの接続後、システムステータスを監視するスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'ログ';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,60 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Al in de laatst gebruikte map.';
|
String get alreadyLastDir => 'Al in de laatst gebruikte map.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'AI vragen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API-sleutel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Wachten op AI-reactie...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Basis-URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Commando in terminal ingevoegd';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Configureer $fields in de instellingen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Bevestigen voor uitvoeren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'AI-gesprek';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'AI kan fouten maken. Gebruik het zorgvuldig.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Stel een vervolgvraag...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'In terminal invoegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Geen reactie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Door AI voorgestelde opdracht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Geselecteerde inhoud';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Gebruikt in de SSH-terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab =>
|
||||||
|
'Er moet minimaal één tabblad worden geselecteerd';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.';
|
'Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'Automatische update van home-widget';
|
String get autoUpdateHomeWidget => 'Automatische update van home-widget';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Beschikbare tabbladen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Back-up is versleuteld';
|
String get backupEncrypted => 'Back-up is versleuteld';
|
||||||
|
|
||||||
@@ -84,6 +141,26 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".';
|
'Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Weet u zeker dat u alle serververbindingsstatistieken wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Alle statistieken wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Weet u zeker dat u de verbindingsstatistieken voor server \"$serverName\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Statistieken van $serverName wissen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Statistieken van deze server wissen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Opslaan en sluiten';
|
String get closeAfterSave => 'Opslaan en sluiten';
|
||||||
|
|
||||||
@@ -97,6 +174,16 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Verbinding';
|
String get conn => 'Verbinding';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Verbindingsdetails';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Verbindingsstatistieken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Bekijk server verbindingssucces ratio en geschiedenis';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Container';
|
String get container => 'Container';
|
||||||
|
|
||||||
@@ -146,6 +233,18 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Verbroken';
|
String get disconnected => 'Verbroken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'SSH-servers ontdekken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Ontdekking mislukt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Ontdekkingsinstellingen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Ontdekkingssamenvatting';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Schijf';
|
String get disk => 'Schijf';
|
||||||
|
|
||||||
@@ -198,9 +297,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Virtuele toetsen bewerken';
|
String get editVirtKeys => 'Virtuele toetsen bewerken';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.';
|
'De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.';
|
||||||
@@ -208,6 +304,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulator';
|
String get emulator => 'Emulator';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'mDNS inschakelen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'Gebruik mDNS/Bonjour om SSH-services te ontdekken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Coderen';
|
String get encode => 'Coderen';
|
||||||
|
|
||||||
@@ -240,10 +343,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Volg systeem';
|
String get finishedAt => 'Voltooid om';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Lettertype';
|
String get followSystem => 'Volg systeem';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Lettergrootte';
|
String get fontSize => 'Lettergrootte';
|
||||||
@@ -276,6 +379,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Code-highlight';
|
String get highlight => 'Code-highlight';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Home-tabbladen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Home-widget-url configureren';
|
String get homeWidgetUrlConfig => 'Home-widget-url configureren';
|
||||||
|
|
||||||
@@ -296,9 +406,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Lijst met afbeeldingen';
|
String get imagesList => 'Lijst met afbeeldingen';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Initialiseren';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Intern';
|
String get inner => 'Intern';
|
||||||
|
|
||||||
@@ -328,6 +435,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Sleutelauthenticatie';
|
String get keyAuth => 'Sleutelauthenticatie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Laatst gefaald';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Laatst succesvol';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Lettercaching';
|
String get letterCache => 'Lettercaching';
|
||||||
|
|
||||||
@@ -335,9 +448,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.';
|
'Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Licentie';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Locatie';
|
String get location => 'Locatie';
|
||||||
|
|
||||||
@@ -350,10 +460,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Handleiding';
|
String get max => 'max';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'max';
|
String get maxConcurrency => 'Maximale gelijktijdigheid';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Aantal serverherverbindingen';
|
String get maxRetryCount => 'Aantal serverherverbindingen';
|
||||||
@@ -393,6 +503,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Nieuwe container';
|
String get newContainer => 'Nieuwe container';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Geen verbindingsstatistiekgegevens';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'lijndiagrammen gebruiken';
|
String get noLineChart => 'lijndiagrammen gebruiken';
|
||||||
|
|
||||||
@@ -465,10 +578,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
'Geef de schijfcapaciteit prioriteit bij weergave';
|
'Geef de schijfcapaciteit prioriteit bij weergave';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Voorbeeld';
|
String get privateKey => 'Privésleutel';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Privésleutel';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Privésleutel [$keyId] niet gevonden.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Proces';
|
String get process => 'Proces';
|
||||||
@@ -497,6 +612,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Herstart';
|
String get reboot => 'Herstart';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Recente verbindingen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Wachtwoord onthouden in geheugen';
|
String get rememberPwdInMem => 'Wachtwoord onthouden in geheugen';
|
||||||
|
|
||||||
@@ -558,6 +676,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Servervolgorde';
|
String get serverOrder => 'Servervolgorde';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Servertabblad kan niet worden verwijderd';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'servers';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Voorbereiden om verbinding te maken...';
|
String get sftpDlPrepare => 'Voorbereiden om verbinding te maken...';
|
||||||
|
|
||||||
@@ -644,6 +768,34 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
return '$count servers geïmporteerd uit SSH-configuratie';
|
return '$count servers geïmporteerd uit SSH-configuratie';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'De SSH-hostsleutel voor $serverName is gewijzigd. Ga alleen verder als u deze server vertrouwt.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Vingerafdruk (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Vingerafdruk (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Type SSH-hostsleutel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Er is een nieuwe SSH-hostsleutel ontvangen van $serverName. Controleer de vingerafdruk voordat u vertrouwt.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Opgeslagen vingerafdruk: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Wilt u het SSH-configuratiebestand handmatig selecteren?';
|
'Wilt u het SSH-configuratiebestand handmatig selecteren?';
|
||||||
@@ -707,9 +859,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
return 'Overschakelen naar $val';
|
return 'Overschakelen naar $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sync';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.';
|
'Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.';
|
||||||
@@ -720,6 +869,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Labels';
|
String get tag => 'Labels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Tik op de zoekknop om SSH-servers op uw netwerk te ontdekken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Temperatuur';
|
String get temperature => 'Temperatuur';
|
||||||
|
|
||||||
@@ -752,6 +905,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Totaal';
|
String get total => 'Totaal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Totaal';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Verkeer';
|
String get traffic => 'Verkeer';
|
||||||
|
|
||||||
@@ -778,9 +934,6 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Interne server status bijwerking interval';
|
'Interne server status bijwerking interval';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Upload';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Ondersteboven';
|
String get upsideDown => 'Ondersteboven';
|
||||||
|
|
||||||
@@ -806,6 +959,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Weergave';
|
String get view => 'Weergave';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Details bekijken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Zie foutmelding';
|
String get viewErr => 'Zie foutmelding';
|
||||||
|
|
||||||
@@ -849,4 +1005,28 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.';
|
'Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Logboeken';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,59 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Já é o diretório mais alto';
|
String get alreadyLastDir => 'Já é o diretório mais alto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Perguntar à IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Chave de API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Aguardando resposta da IA...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'URL base';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Comando inserido no terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Configure $fields nas configurações.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Confirmar antes de executar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Conversa com a IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'A IA pode errar. Use com cautela.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Faça uma pergunta adicional...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Inserir no terminal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Modelo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Sem resposta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Comando sugerido pela IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Conteúdo selecionado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Usado no terminal SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.';
|
'Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.';
|
||||||
@@ -45,6 +98,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get autoUpdateHomeWidget =>
|
String get autoUpdateHomeWidget =>
|
||||||
'Atualização automática do widget da tela inicial';
|
'Atualização automática do widget da tela inicial';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Abas disponíveis';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Backup está criptografado';
|
String get backupEncrypted => 'Backup está criptografado';
|
||||||
|
|
||||||
@@ -85,6 +141,26 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a \'Otimização de bateria\' para este app, no MIUI, altere a estratégia de economia de energia para \'Sem restrições\'.';
|
'Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a \'Otimização de bateria\' para este app, no MIUI, altere a estratégia de economia de energia para \'Sem restrições\'.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Tem certeza de que deseja limpar todas as estatísticas de conexão do servidor? Esta ação não pode ser desfeita.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Limpar todas as estatísticas';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"$serverName\"? Esta ação não pode ser desfeita.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Limpar estatísticas de $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Limpar estatísticas deste servidor';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Salvar e fechar';
|
String get closeAfterSave => 'Salvar e fechar';
|
||||||
|
|
||||||
@@ -97,6 +173,16 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Conectar';
|
String get conn => 'Conectar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Detalhes da conexão';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Estatísticas de conexão';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Ver taxa de sucesso de conexão do servidor e histórico';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Contêiner';
|
String get container => 'Contêiner';
|
||||||
|
|
||||||
@@ -146,6 +232,18 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Desconectado';
|
String get disconnected => 'Desconectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Descobrir servidores SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Descoberta falhou';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Configurações de descoberta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Resumo da descoberta';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disco';
|
String get disk => 'Disco';
|
||||||
|
|
||||||
@@ -198,9 +296,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Editar teclas virtuais';
|
String get editVirtKeys => 'Editar teclas virtuais';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Editor';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.';
|
'O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.';
|
||||||
@@ -208,6 +303,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emulador';
|
String get emulator => 'Emulador';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Ativar mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => 'Usar mDNS/Bonjour para descobrir serviços SSH';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Codificar';
|
String get encode => 'Codificar';
|
||||||
|
|
||||||
@@ -240,10 +341,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Seguir sistema';
|
String get finishedAt => 'Terminado em';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Fonte';
|
String get followSystem => 'Seguir sistema';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Tamanho da fonte';
|
String get fontSize => 'Tamanho da fonte';
|
||||||
@@ -276,6 +377,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Destaque de código';
|
String get highlight => 'Destaque de código';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Abas iniciais';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Personalize quais abas aparecem na página inicial e sua ordem';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig =>
|
String get homeWidgetUrlConfig =>
|
||||||
'Configuração de URL do widget da tela inicial';
|
'Configuração de URL do widget da tela inicial';
|
||||||
@@ -297,9 +405,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Lista de imagens';
|
String get imagesList => 'Lista de imagens';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Inicializar';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Interno';
|
String get inner => 'Interno';
|
||||||
|
|
||||||
@@ -328,6 +433,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Autenticação por chave';
|
String get keyAuth => 'Autenticação por chave';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Última falha';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Último sucesso';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Cache de letras';
|
String get letterCache => 'Cache de letras';
|
||||||
|
|
||||||
@@ -335,9 +446,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.';
|
'Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Licença de código aberto';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Localização';
|
String get location => 'Localização';
|
||||||
|
|
||||||
@@ -350,10 +458,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manual';
|
String get max => 'Máximo';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'Máximo';
|
String get maxConcurrency => 'Concorrência máxima';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount =>
|
String get maxRetryCount =>
|
||||||
@@ -394,6 +502,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Novo contêiner';
|
String get newContainer => 'Novo contêiner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Não há dados de estatísticas de conexão';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Não usar gráficos de linha';
|
String get noLineChart => 'Não usar gráficos de linha';
|
||||||
|
|
||||||
@@ -465,10 +576,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Priorizar a exibição da capacidade do disco';
|
String get preferDiskAmount => 'Priorizar a exibição da capacidade do disco';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Pré-visualização';
|
String get privateKey => 'Chave privada';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Chave privada';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Chave privada [$keyId] não encontrada.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Processo';
|
String get process => 'Processo';
|
||||||
@@ -497,6 +610,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Reiniciar';
|
String get reboot => 'Reiniciar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Conexões recentes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Lembrar senha na memória';
|
String get rememberPwdInMem => 'Lembrar senha na memória';
|
||||||
|
|
||||||
@@ -558,6 +674,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Ordem do servidor';
|
String get serverOrder => 'Ordem do servidor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'A aba do servidor não pode ser removida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'servidores';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Preparando para conectar ao servidor...';
|
String get sftpDlPrepare => 'Preparando para conectar ao servidor...';
|
||||||
|
|
||||||
@@ -642,6 +764,34 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
return 'Importados $count servidores da configuração SSH';
|
return 'Importados $count servidores da configuração SSH';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'A chave de host SSH de $serverName foi alterada. Continue apenas se confiar neste servidor.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Impressão digital (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Impressão digital (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Tipo de chave de host SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Uma nova chave de host SSH foi recebida de $serverName. Verifique a impressão digital antes de confiar.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Impressão digital armazenada: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Gostaria de selecionar manualmente o arquivo de configuração SSH?';
|
'Gostaria de selecionar manualmente o arquivo de configuração SSH?';
|
||||||
@@ -705,9 +855,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
return 'Mudar para $val';
|
return 'Mudar para $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Sincronizar';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Pode ser necessário reiniciar para algumas mudanças surtirem efeito.';
|
'Pode ser necessário reiniciar para algumas mudanças surtirem efeito.';
|
||||||
@@ -718,6 +865,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Tag';
|
String get tag => 'Tag';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Toque no botão de pesquisa para descobrir servidores SSH na sua rede';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Temperatura';
|
String get temperature => 'Temperatura';
|
||||||
|
|
||||||
@@ -750,6 +901,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Total';
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Tráfego';
|
String get traffic => 'Tráfego';
|
||||||
|
|
||||||
@@ -776,9 +930,6 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Intervalo de atualização do estado do servidor';
|
'Intervalo de atualização do estado do servidor';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Upload';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Inverter verticalmente';
|
String get upsideDown => 'Inverter verticalmente';
|
||||||
|
|
||||||
@@ -804,6 +955,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Visualização';
|
String get view => 'Visualização';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Ver detalhes';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Ver erro';
|
String get viewErr => 'Ver erro';
|
||||||
|
|
||||||
@@ -846,4 +1000,28 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.';
|
'Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Logs';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,60 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Уже в корневом каталоге';
|
String get alreadyLastDir => 'Уже в корневом каталоге';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Спросить ИИ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Ключ API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Ожидание ответа ИИ...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Базовый URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Команда вставлена в терминал';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Настройте $fields в настройках.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Подтвердите перед выполнением';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Разговор с ИИ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'ИИ может ошибаться. Используйте с осторожностью.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Задайте дополнительный вопрос...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Вставить в терминал';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Модель';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Нет ответа';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Команда, предложенная ИИ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Выбранное содержимое';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Используется в SSH-терминале';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.';
|
'Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.';
|
||||||
@@ -45,6 +99,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get autoUpdateHomeWidget =>
|
String get autoUpdateHomeWidget =>
|
||||||
'Автоматическое обновление виджета на главном экране';
|
'Автоматическое обновление виджета на главном экране';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Доступные вкладки';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Резервная копия зашифрована';
|
String get backupEncrypted => 'Резервная копия зашифрована';
|
||||||
|
|
||||||
@@ -85,6 +142,26 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».';
|
'Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Вы уверены, что хотите очистить всю статистику соединений сервера? Это действие не может быть отменено.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Очистить всю статистику';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Вы уверены, что хотите очистить статистику соединений для сервера \"$serverName\"? Это действие не может быть отменено.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Очистить статистику $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Очистить статистику этого сервера';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Сохранить и закрыть';
|
String get closeAfterSave => 'Сохранить и закрыть';
|
||||||
|
|
||||||
@@ -97,6 +174,16 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Подключение';
|
String get conn => 'Подключение';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Детали соединения';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Статистика соединений';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Просмотр коэффициента успешности подключения к серверу и истории';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Контейнер';
|
String get container => 'Контейнер';
|
||||||
|
|
||||||
@@ -146,6 +233,18 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Отключено';
|
String get disconnected => 'Отключено';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Обнаружить SSH серверы';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Обнаружение не удалось';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Настройки обнаружения';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Сводка обнаружения';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Диск';
|
String get disk => 'Диск';
|
||||||
|
|
||||||
@@ -198,9 +297,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Редактировать виртуальные клавиши';
|
String get editVirtKeys => 'Редактировать виртуальные клавиши';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Редактор';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.';
|
'Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.';
|
||||||
@@ -208,6 +304,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Эмулятор';
|
String get emulator => 'Эмулятор';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Включить mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'Использовать mDNS/Bonjour для обнаружения SSH служб';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Кодировать';
|
String get encode => 'Кодировать';
|
||||||
|
|
||||||
@@ -240,10 +343,10 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Следовать за системой';
|
String get finishedAt => 'Завершено в';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Шрифт';
|
String get followSystem => 'Следовать за системой';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Размер шрифта';
|
String get fontSize => 'Размер шрифта';
|
||||||
@@ -276,6 +379,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Подсветка кода';
|
String get highlight => 'Подсветка кода';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Вкладки дома';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Настройте, какие вкладки появляются на главной странице и их порядок';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Конфигурация URL виджета домашнего экрана';
|
String get homeWidgetUrlConfig => 'Конфигурация URL виджета домашнего экрана';
|
||||||
|
|
||||||
@@ -296,9 +406,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Список образов';
|
String get imagesList => 'Список образов';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Инициализировать';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Встроенный';
|
String get inner => 'Встроенный';
|
||||||
|
|
||||||
@@ -328,6 +435,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Аутентификация по ключу';
|
String get keyAuth => 'Аутентификация по ключу';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Последний сбой';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Последний успех';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Кэширование букв';
|
String get letterCache => 'Кэширование букв';
|
||||||
|
|
||||||
@@ -335,9 +448,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.';
|
'Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Лицензия';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Местоположение';
|
String get location => 'Местоположение';
|
||||||
|
|
||||||
@@ -350,10 +460,10 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Вручную';
|
String get max => 'максимум';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'максимум';
|
String get maxConcurrency => 'Максимальная параллельность';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount =>
|
String get maxRetryCount =>
|
||||||
@@ -395,6 +505,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Создать контейнер';
|
String get newContainer => 'Создать контейнер';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Нет данных статистики соединений';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Не использовать линейные графики';
|
String get noLineChart => 'Не использовать линейные графики';
|
||||||
|
|
||||||
@@ -466,10 +579,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Приоритетное отображение объёма диска';
|
String get preferDiskAmount => 'Приоритетное отображение объёма диска';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Предпросмотр';
|
String get privateKey => 'Приватный ключ';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Приватный ключ';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Закрытый ключ [$keyId] не найден.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Процесс';
|
String get process => 'Процесс';
|
||||||
@@ -498,6 +613,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Перезагрузка';
|
String get reboot => 'Перезагрузка';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Недавние соединения';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Запомнить пароль в памяти';
|
String get rememberPwdInMem => 'Запомнить пароль в памяти';
|
||||||
|
|
||||||
@@ -560,6 +678,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Порядок серверов';
|
String get serverOrder => 'Порядок серверов';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Вкладку сервера нельзя удалить';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'серверов';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Подготовка подключения...';
|
String get sftpDlPrepare => 'Подготовка подключения...';
|
||||||
|
|
||||||
@@ -645,6 +769,34 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
return 'Импортировано $count серверов из SSH-конфигурации';
|
return 'Импортировано $count серверов из SSH-конфигурации';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'SSH-ключ хоста для $serverName изменился. Продолжайте только если доверяете этому серверу.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Отпечаток (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Отпечаток (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Тип ключа хоста SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Получен новый SSH-ключ хоста от $serverName. Проверьте отпечаток перед продолжением.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Сохранённый отпечаток: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Хотели бы вы вручную выбрать файл конфигурации SSH?';
|
'Хотели бы вы вручную выбрать файл конфигурации SSH?';
|
||||||
@@ -707,9 +859,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
return 'Переключиться на $val';
|
return 'Переключиться на $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Синхронизировать';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.';
|
'Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.';
|
||||||
@@ -720,6 +869,10 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Теги';
|
String get tag => 'Теги';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Нажмите кнопку поиска, чтобы обнаружить SSH серверы в вашей сети';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Температура';
|
String get temperature => 'Температура';
|
||||||
|
|
||||||
@@ -752,6 +905,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Всего';
|
String get total => 'Всего';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Общее';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Трафик';
|
String get traffic => 'Трафик';
|
||||||
|
|
||||||
@@ -778,9 +934,6 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get updateServerStatusInterval =>
|
String get updateServerStatusInterval =>
|
||||||
'Интервал обновления статуса сервера';
|
'Интервал обновления статуса сервера';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Загрузить';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Перевернуть';
|
String get upsideDown => 'Перевернуть';
|
||||||
|
|
||||||
@@ -806,6 +959,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Вид';
|
String get view => 'Вид';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Просмотр деталей';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Просмотр ошибок';
|
String get viewErr => 'Просмотр ошибок';
|
||||||
|
|
||||||
@@ -848,4 +1004,28 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.';
|
'После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Журналы';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,60 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Zaten son dizindesiniz.';
|
String get alreadyLastDir => 'Zaten son dizindesiniz.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Yapay zekaya sor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API anahtarı';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Yapay zekâ yanıtı bekleniyor...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Temel URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Komut terminale eklendi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Lütfen Ayarlar\'da $fields öğesini yapılandırın.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Çalıştırmadan önce onayla';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'YZ sohbeti';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer =>
|
||||||
|
'Yapay zeka hata yapabilir. Lütfen dikkatli kullanın.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Yeni bir soru sor...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Terminale ekle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Yanıt yok';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'YZ önerilen komut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Seçilen içerik';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'SSH Terminalinde kullanılır';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'En az bir sekme seçilmelidir';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin';
|
'Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin';
|
||||||
@@ -44,6 +98,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => 'Ana ekran bileşenini otomatik güncelle';
|
String get autoUpdateHomeWidget => 'Ana ekran bileşenini otomatik güncelle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Mevcut Sekmeler';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Yedekleme şifrelenmiş';
|
String get backupEncrypted => 'Yedekleme şifrelenmiş';
|
||||||
|
|
||||||
@@ -83,6 +140,26 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM\'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.';
|
'Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM\'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Tüm sunucu bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Tüm İstatistikleri Temizle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return '\"$serverName\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return '$serverName İstatistiklerini Temizle';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Bu Sunucu İstatistiklerini Temizle';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Kaydet ve kapat';
|
String get closeAfterSave => 'Kaydet ve kapat';
|
||||||
|
|
||||||
@@ -96,6 +173,16 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'Bağlantı';
|
String get conn => 'Bağlantı';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Bağlantı Detayları';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Bağlantı İstatistikleri';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Sunucu bağlantı başarı oranını ve geçmişi görüntüle';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Konteyner';
|
String get container => 'Konteyner';
|
||||||
|
|
||||||
@@ -145,6 +232,18 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Bağlantı kesildi';
|
String get disconnected => 'Bağlantı kesildi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'SSH Sunucularını Keşfet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Keşif başarısız';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Keşif Ayarları';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Keşif Özeti';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Disk';
|
String get disk => 'Disk';
|
||||||
|
|
||||||
@@ -197,9 +296,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Sanal tuşları düzenle';
|
String get editVirtKeys => 'Sanal tuşları düzenle';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Düzenleyici';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.';
|
'Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.';
|
||||||
@@ -207,6 +303,13 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Emülatör';
|
String get emulator => 'Emülatör';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'mDNS\'yi Etkinleştir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'SSH hizmetlerini keşfetmek için mDNS/Bonjour kullan';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Kodla';
|
String get encode => 'Kodla';
|
||||||
|
|
||||||
@@ -239,10 +342,10 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Sistemi takip et';
|
String get finishedAt => 'Tamamlandı:';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Yazı tipi';
|
String get followSystem => 'Sistemi takip et';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Yazı tipi boyutu';
|
String get fontSize => 'Yazı tipi boyutu';
|
||||||
@@ -275,6 +378,13 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Kod vurgulama';
|
String get highlight => 'Kod vurgulama';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Ana Sayfa Sekmeleri';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => 'Ana ekran bileşeni URL\'sini yapılandır';
|
String get homeWidgetUrlConfig => 'Ana ekran bileşeni URL\'sini yapılandır';
|
||||||
|
|
||||||
@@ -295,9 +405,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Görüntü listesi';
|
String get imagesList => 'Görüntü listesi';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Başlat';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'İç';
|
String get inner => 'İç';
|
||||||
|
|
||||||
@@ -327,6 +434,12 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Anahtar Kimlik Doğrulama';
|
String get keyAuth => 'Anahtar Kimlik Doğrulama';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Son Başarısızlık';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Son Başarı';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Harf önbelleği';
|
String get letterCache => 'Harf önbelleği';
|
||||||
|
|
||||||
@@ -334,9 +447,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.';
|
'Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Lisans';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Konum';
|
String get location => 'Konum';
|
||||||
|
|
||||||
@@ -349,10 +459,10 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Manuel';
|
String get max => 'maks';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'maks';
|
String get maxConcurrency => 'Maksimum Eşzamanlılık';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => 'Sunucu yeniden bağlantı sayısı';
|
String get maxRetryCount => 'Sunucu yeniden bağlantı sayısı';
|
||||||
@@ -392,6 +502,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Yeni konteyner';
|
String get newContainer => 'Yeni konteyner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Bağlantı istatistik verisi yok';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Çizgi grafikleri kullanma';
|
String get noLineChart => 'Çizgi grafikleri kullanma';
|
||||||
|
|
||||||
@@ -463,10 +576,12 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Disk kapasitesini öncelikli olarak göster';
|
String get preferDiskAmount => 'Disk kapasitesini öncelikli olarak göster';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Önizleme';
|
String get privateKey => 'Özel Anahtar';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Özel Anahtar';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Özel anahtar [$keyId] bulunamadı.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'İşlem';
|
String get process => 'İşlem';
|
||||||
@@ -495,6 +610,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Yeniden başlat';
|
String get reboot => 'Yeniden başlat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Son Bağlantılar';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Şifreyi bellekte hatırla';
|
String get rememberPwdInMem => 'Şifreyi bellekte hatırla';
|
||||||
|
|
||||||
@@ -556,6 +674,12 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Sunucu sırası';
|
String get serverOrder => 'Sunucu sırası';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Sunucu sekmesi kaldırılamaz';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'sunucu';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Bağlantı hazırlanıyor...';
|
String get sftpDlPrepare => 'Bağlantı hazırlanıyor...';
|
||||||
|
|
||||||
@@ -641,6 +765,34 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
return 'SSH yapılandırmasından $count sunucu içe aktarıldı';
|
return 'SSH yapılandırmasından $count sunucu içe aktarıldı';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return '$serverName için SSH ana bilgisayar anahtarı değişti. Yalnızca bu sunucuya güveniyorsanız devam edin.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Parmak izi (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Parmak izi (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH ana bilgisayar anahtarı türü';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return '$serverName üzerinden yeni bir SSH ana bilgisayar anahtarı alındı. Güvenmeden önce parmak izini kontrol edin.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Kaydedilen parmak izi: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?';
|
'SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?';
|
||||||
@@ -703,9 +855,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
return '$val\'a geç';
|
return '$val\'a geç';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Senkronize et';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.';
|
'Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.';
|
||||||
@@ -716,6 +865,10 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Etiketler';
|
String get tag => 'Etiketler';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Ağınızdaki SSH sunucularını keşfetmek için arama düğmesine dokunun';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Sıcaklık';
|
String get temperature => 'Sıcaklık';
|
||||||
|
|
||||||
@@ -748,6 +901,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Toplam';
|
String get total => 'Toplam';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Toplam';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Trafik';
|
String get traffic => 'Trafik';
|
||||||
|
|
||||||
@@ -773,9 +929,6 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => 'Sunucu durumu güncelleme aralığı';
|
String get updateServerStatusInterval => 'Sunucu durumu güncelleme aralığı';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Yükle';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Başaşağı';
|
String get upsideDown => 'Başaşağı';
|
||||||
|
|
||||||
@@ -801,6 +954,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Görünüm';
|
String get view => 'Görünüm';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Detayları Görüntüle';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Hatayı gör';
|
String get viewErr => 'Hatayı gör';
|
||||||
|
|
||||||
@@ -843,4 +999,28 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.';
|
'Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Günlükler';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,59 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => 'Вже в останньому каталозі.';
|
String get alreadyLastDir => 'Вже в останньому каталозі.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => 'Запитати ШІ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'Ключ API';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => 'Очікування відповіді ШІ...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => 'Базова URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => 'Команду вставлено в термінал';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return 'Налаштуйте $fields у налаштуваннях.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => 'Підтвердити перед виконанням';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'Розмова з ШІ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'ШІ може помилятися. Користуйтеся обережно.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => 'Поставте додаткове запитання...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => 'Вставити в термінал';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => 'Модель';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => 'Відповідь відсутня';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'Команда, запропонована ШІ';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => 'Вибраний вміст';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => 'Використовується в SSH-терміналі';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip =>
|
String get authFailTip =>
|
||||||
'Авторизація не вдалася, будь ласка, перевірте правильність облікових даних';
|
'Авторизація не вдалася, будь ласка, перевірте правильність облікових даних';
|
||||||
@@ -45,6 +98,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get autoUpdateHomeWidget =>
|
String get autoUpdateHomeWidget =>
|
||||||
'Автоматичне оновлення віджетів на головному екрані';
|
'Автоматичне оновлення віджетів на головному екрані';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => 'Доступні вкладки';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => 'Резервна копія зашифрована';
|
String get backupEncrypted => 'Резервна копія зашифрована';
|
||||||
|
|
||||||
@@ -85,6 +141,26 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".';
|
'Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent =>
|
||||||
|
'Ви впевнені, що хочете очистити всю статистику з\'єднань сервера? Цю дію не можна скасувати.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => 'Очистити всю статистику';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return 'Ви впевнені, що хочете очистити статистику з\'єднань для сервера \"$serverName\"? Цю дію не можна скасувати.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return 'Очистити статистику $serverName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => 'Очистити статистику цього сервера';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => 'Зберегти та закрити';
|
String get closeAfterSave => 'Зберегти та закрити';
|
||||||
|
|
||||||
@@ -98,6 +174,16 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => 'З\'єднання';
|
String get conn => 'З\'єднання';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => 'Деталі з\'єднання';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => 'Статистика з\'єднань';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc =>
|
||||||
|
'Переглянути коефіцієнт успішності підключення до сервера та історію';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => 'Контейнер';
|
String get container => 'Контейнер';
|
||||||
|
|
||||||
@@ -147,6 +233,18 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => 'Відключено';
|
String get disconnected => 'Відключено';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => 'Виявити SSH сервери';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => 'Виявлення не вдалось';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => 'Налаштування виявлення';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => 'Підсумок виявлення';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => 'Диск';
|
String get disk => 'Диск';
|
||||||
|
|
||||||
@@ -199,9 +297,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => 'Редагувати віртуальні клавіші';
|
String get editVirtKeys => 'Редагувати віртуальні клавіші';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => 'Редактор';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip =>
|
String get editorHighlightTip =>
|
||||||
'Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.';
|
'Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.';
|
||||||
@@ -209,6 +304,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get emulator => 'Емулятор';
|
String get emulator => 'Емулятор';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => 'Увімкнути mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc =>
|
||||||
|
'Використовувати mDNS/Bonjour для виявлення SSH сервісів';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => 'Кодувати';
|
String get encode => 'Кодувати';
|
||||||
|
|
||||||
@@ -241,10 +343,10 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => 'Слідувати системі';
|
String get finishedAt => 'Завершено о';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => 'Шрифт';
|
String get followSystem => 'Слідувати системі';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Розмір шрифту';
|
String get fontSize => 'Розмір шрифту';
|
||||||
@@ -277,6 +379,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => 'Підсвітка коду';
|
String get highlight => 'Підсвітка коду';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => 'Домашні вкладки';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc =>
|
||||||
|
'Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig =>
|
String get homeWidgetUrlConfig =>
|
||||||
'Налаштувати URL віджета на головному екрані';
|
'Налаштувати URL віджета на головному екрані';
|
||||||
@@ -298,9 +407,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => 'Список зображень';
|
String get imagesList => 'Список зображень';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => 'Ініціалізувати';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => 'Внутрішній';
|
String get inner => 'Внутрішній';
|
||||||
|
|
||||||
@@ -330,6 +436,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => 'Аутентифікація ключем';
|
String get keyAuth => 'Аутентифікація ключем';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => 'Остання помилка';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => 'Останній успіх';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => 'Кешування букв';
|
String get letterCache => 'Кешування букв';
|
||||||
|
|
||||||
@@ -337,9 +449,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get letterCacheTip =>
|
String get letterCacheTip =>
|
||||||
'Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.';
|
'Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => 'Ліцензія';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => 'Місцезнаходження';
|
String get location => 'Місцезнаходження';
|
||||||
|
|
||||||
@@ -352,10 +461,10 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => 'Посібник';
|
String get max => 'макс.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => 'макс.';
|
String get maxConcurrency => 'Максимальна паралельність';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount =>
|
String get maxRetryCount =>
|
||||||
@@ -397,6 +506,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => 'Новий контейнер';
|
String get newContainer => 'Новий контейнер';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => 'Немає даних статистики з\'єднань';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => 'Не використовувати лінійні діаграми';
|
String get noLineChart => 'Не використовувати лінійні діаграми';
|
||||||
|
|
||||||
@@ -468,10 +580,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get preferDiskAmount => 'Пріоритетно показувати ємність диска';
|
String get preferDiskAmount => 'Пріоритетно показувати ємність диска';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => 'Попередній перегляд';
|
String get privateKey => 'Приватний ключ';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => 'Приватний ключ';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return 'Приватний ключ [$keyId] не знайдено.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => 'Процес';
|
String get process => 'Процес';
|
||||||
@@ -500,6 +614,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => 'Перезавантажити';
|
String get reboot => 'Перезавантажити';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => 'Останні з\'єднання';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => 'Запам\'ятати пароль у пам\'яті';
|
String get rememberPwdInMem => 'Запам\'ятати пароль у пам\'яті';
|
||||||
|
|
||||||
@@ -561,6 +678,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => 'Порядок сервера';
|
String get serverOrder => 'Порядок сервера';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => 'Вкладку сервера не можна видалити';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => 'серверів';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => 'Підготовка до підключення...';
|
String get sftpDlPrepare => 'Підготовка до підключення...';
|
||||||
|
|
||||||
@@ -646,6 +769,34 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
return 'Імпортовано $count серверів з SSH-конфігурації';
|
return 'Імпортовано $count серверів з SSH-конфігурації';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return 'SSH-ключ хоста для $serverName змінено. Продовжуйте лише якщо довіряєте цьому серверу.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return 'Відбиток (MD5 Base64): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return 'Відбиток (MD5 hex): $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'Тип ключа хоста SSH';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return 'Отримано новий SSH-ключ хоста від $serverName. Перевірте відбиток перед тим, як довіряти.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return 'Збережений відбиток: $fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect =>
|
String get sshConfigManualSelect =>
|
||||||
'Чи хочете ви вручну вибрати файл конфігурації SSH?';
|
'Чи хочете ви вручну вибрати файл конфігурації SSH?';
|
||||||
@@ -708,9 +859,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
return 'Переключитися на $val';
|
return 'Переключитися на $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => 'Синхронізація';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip =>
|
String get syncTip =>
|
||||||
'Може знадобитися перезапуск, щоб деякі зміни набрали чинності.';
|
'Може знадобитися перезапуск, щоб деякі зміни набрали чинності.';
|
||||||
@@ -721,6 +869,10 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => 'Теги';
|
String get tag => 'Теги';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery =>
|
||||||
|
'Натисніть кнопку пошуку, щоб виявити SSH сервери у вашій мережі';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => 'Температура';
|
String get temperature => 'Температура';
|
||||||
|
|
||||||
@@ -753,6 +905,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => 'Всього';
|
String get total => 'Всього';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => 'Загальна кількість';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => 'Трафік';
|
String get traffic => 'Трафік';
|
||||||
|
|
||||||
@@ -778,9 +933,6 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => 'Інтервал оновлення статусу сервера';
|
String get updateServerStatusInterval => 'Інтервал оновлення статусу сервера';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => 'Завантаження';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => 'Доверху дном';
|
String get upsideDown => 'Доверху дном';
|
||||||
|
|
||||||
@@ -806,6 +958,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => 'Переглянути';
|
String get view => 'Переглянути';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => 'Переглянути деталі';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => 'Переглянути помилку';
|
String get viewErr => 'Переглянути помилку';
|
||||||
|
|
||||||
@@ -849,4 +1004,28 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.';
|
'Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => 'Setting';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => 'Quit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => 'Navigate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub Repository';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => 'Help';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => 'Журнали';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,59 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => '已是顶级目录';
|
String get alreadyLastDir => '已是顶级目录';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => '问 AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API 密钥';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => '等待 AI 响应...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => '基础 URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => '命令已插入终端';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return '请前往设置配置 $fields';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => '执行前确认';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'AI 对话';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'AI 可能会犯错,请谨慎使用。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => '继续提问...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => '插入终端';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => '模型';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => '无回复内容';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'AI 推荐命令';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => '选中的内容';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => '用于 SSH 终端';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '至少需要选择一个标签';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip => '认证失败,请检查连接信息是否正确';
|
String get authFailTip => '认证失败,请检查连接信息是否正确';
|
||||||
|
|
||||||
@@ -41,6 +94,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => '自动更新桌面小部件';
|
String get autoUpdateHomeWidget => '自动更新桌面小部件';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '可用标签';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => '备份已加密';
|
String get backupEncrypted => '备份已加密';
|
||||||
|
|
||||||
@@ -78,6 +134,25 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”,MIUI / HyperOS 请将省电策略改为“无限制”。';
|
'此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”,MIUI / HyperOS 请将省电策略改为“无限制”。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent => '确定要清空所有服务器的连接统计数据吗?此操作无法撤销。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => '清空所有统计';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return '确定要清空服务器 \"$serverName\" 的连接统计数据吗?此操作无法撤销。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return '清空 $serverName 统计';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => '清空此服务器统计';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => '保存后关闭';
|
String get closeAfterSave => '保存后关闭';
|
||||||
|
|
||||||
@@ -90,6 +165,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get conn => '连接';
|
String get conn => '连接';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => '连接详情';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => '连接统计';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc => '查看服务器连接成功率和历史记录';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => '容器';
|
String get container => '容器';
|
||||||
|
|
||||||
@@ -137,6 +221,18 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => '已断开连接';
|
String get disconnected => '已断开连接';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => '发现SSH服务器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => '发现失败';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => '发现设置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => '发现摘要';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => '磁盘';
|
String get disk => '磁盘';
|
||||||
|
|
||||||
@@ -188,15 +284,18 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => '编辑虚拟按键';
|
String get editVirtKeys => '编辑虚拟按键';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => '编辑器';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip => '代码高亮功能可能影响性能,可选择关闭。';
|
String get editorHighlightTip => '代码高亮功能可能影响性能,可选择关闭。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emulator => '模拟器';
|
String get emulator => '模拟器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => '启用mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => '使用mDNS/Bonjour发现SSH服务';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => '编码';
|
String get encode => '编码';
|
||||||
|
|
||||||
@@ -228,10 +327,10 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => '跟随系统';
|
String get finishedAt => '完成于';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => '字体';
|
String get followSystem => '跟随系统';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => '字体大小';
|
String get fontSize => '字体大小';
|
||||||
@@ -263,6 +362,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highlight => '代码高亮';
|
String get highlight => '代码高亮';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => '主页标签';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => '自定义主页上显示的标签及其顺序';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => '桌面部件链接配置';
|
String get homeWidgetUrlConfig => '桌面部件链接配置';
|
||||||
|
|
||||||
@@ -283,9 +388,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => '镜像列表';
|
String get imagesList => '镜像列表';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => '初始化';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => '内置';
|
String get inner => '内置';
|
||||||
|
|
||||||
@@ -314,15 +416,18 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => '密钥认证';
|
String get keyAuth => '密钥认证';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => '最后失败';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => '最后成功';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => '输入法字符缓存';
|
String get letterCache => '输入法字符缓存';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCacheTip => '推荐关闭,但是关闭后无法输入 CJK 等文字';
|
String get letterCacheTip => '推荐关闭,但是关闭后无法输入 CJK 等文字';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => '证书';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => '位置';
|
String get location => '位置';
|
||||||
|
|
||||||
@@ -335,10 +440,10 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => '手动';
|
String get max => '最大';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => '最大';
|
String get maxConcurrency => '最大并发数';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => '服务器尝试重连次数';
|
String get maxRetryCount => '服务器尝试重连次数';
|
||||||
@@ -378,6 +483,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => '新建容器';
|
String get newContainer => '新建容器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => '暂无连接统计数据';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => '不使用折线图';
|
String get noLineChart => '不使用折线图';
|
||||||
|
|
||||||
@@ -444,10 +552,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get preferDiskAmount => '优先显示硬盘容量';
|
String get preferDiskAmount => '优先显示硬盘容量';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => '预览';
|
String get privateKey => '私钥';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => '私钥';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return '未找到私钥 [$keyId]。';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => '进程';
|
String get process => '进程';
|
||||||
@@ -473,6 +583,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get reboot => '重启';
|
String get reboot => '重启';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => '最近连接记录';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => '在内存中记住密码';
|
String get rememberPwdInMem => '在内存中记住密码';
|
||||||
|
|
||||||
@@ -533,6 +646,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => '服务器顺序';
|
String get serverOrder => '服务器顺序';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => '服务器标签不能被移除';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => '服务器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => '准备连接至服务器...';
|
String get sftpDlPrepare => '准备连接至服务器...';
|
||||||
|
|
||||||
@@ -613,6 +732,34 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
return '从 SSH 配置导入了 $count 个服务器';
|
return '从 SSH 配置导入了 $count 个服务器';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return '服务器 $serverName 的 SSH 主机密钥已更改,仅在信任该服务器时继续。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return '指纹(MD5 Base64):$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return '指纹(MD5 十六进制):$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH 主机密钥类型';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return '收到来自 $serverName 的新 SSH 主机密钥,在信任前请检查指纹。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return '已存储的指纹:$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect => '是否要手动选择 SSH 配置文件?';
|
String get sshConfigManualSelect => '是否要手动选择 SSH 配置文件?';
|
||||||
|
|
||||||
@@ -671,9 +818,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
return '切换到 $val';
|
return '切换到 $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => '同步';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '可能需要重新启动,某些更改才能生效。';
|
String get syncTip => '可能需要重新启动,某些更改才能生效。';
|
||||||
|
|
||||||
@@ -683,6 +827,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get tag => '标签';
|
String get tag => '标签';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery => '点击搜索按钮发现网络中的SSH服务器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => '温度';
|
String get temperature => '温度';
|
||||||
|
|
||||||
@@ -713,6 +860,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get total => '总共';
|
String get total => '总共';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => '总次数';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => '流量';
|
String get traffic => '流量';
|
||||||
|
|
||||||
@@ -737,9 +887,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => '服务器状态刷新间隔';
|
String get updateServerStatusInterval => '服务器状态刷新间隔';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => '上传';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => '上下交换';
|
String get upsideDown => '上下交换';
|
||||||
|
|
||||||
@@ -764,6 +911,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get view => '视图';
|
String get view => '视图';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => '查看详情';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => '查看错误';
|
String get viewErr => '查看错误';
|
||||||
|
|
||||||
@@ -803,6 +953,30 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。';
|
'在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuSettings => '设置';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuQuit => '退出';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuNavigate => '导航';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuInfo => '信息';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuGitHubRepository => 'GitHub 仓库';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuWiki => 'Wiki';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menuHelp => '帮助';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => '日志';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
||||||
@@ -827,6 +1001,59 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get alreadyLastDir => '已是頂層目錄';
|
String get alreadyLastDir => '已是頂層目錄';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAi => '詢問 AI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiApiKey => 'API 金鑰';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiAwaitingResponse => '等待 AI 回應...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiBaseUrl => '基礎 URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiCommandInserted => '指令已插入終端機';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String askAiConfigMissing(Object fields) {
|
||||||
|
return '請前往設定配置 $fields';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConfirmExecute => '執行前確認';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiConversation => 'AI 對話';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiDisclaimer => 'AI 可能會犯錯,請謹慎使用。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiFollowUpHint => '繼續提問...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiInsertTerminal => '插入終端機';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiModel => '模型';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiNoResponse => '無回覆內容';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiRecommendedCommand => 'AI 推薦指令';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiSelectedContent => '選取的內容';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get askAiUsageHint => '於 SSH 終端機中使用';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get atLeastOneTab => '至少需要選擇一個標籤';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get authFailTip => '認證失敗,請檢查連線資訊是否正確';
|
String get authFailTip => '認證失敗,請檢查連線資訊是否正確';
|
||||||
|
|
||||||
@@ -842,6 +1069,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get autoUpdateHomeWidget => '自動更新桌面小工具';
|
String get autoUpdateHomeWidget => '自動更新桌面小工具';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get availableTabs => '可用標籤';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get backupEncrypted => '備份已加密';
|
String get backupEncrypted => '備份已加密';
|
||||||
|
|
||||||
@@ -879,6 +1109,25 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
String get bgRunTip =>
|
String get bgRunTip =>
|
||||||
'此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。';
|
'此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsContent => '確定要清空所有伺服器的連線統計資料嗎?此操作無法撤銷。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllStatsTitle => '清空所有統計';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsContent(Object serverName) {
|
||||||
|
return '確定要清空伺服器 \"$serverName\" 的連線統計資料嗎?此操作無法撤銷。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clearServerStatsTitle(Object serverName) {
|
||||||
|
return '清空 $serverName 統計';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearThisServerStats => '清空此伺服器統計';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get closeAfterSave => '儲存後關閉';
|
String get closeAfterSave => '儲存後關閉';
|
||||||
|
|
||||||
@@ -891,6 +1140,15 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get conn => '連線';
|
String get conn => '連線';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionDetails => '連線詳情';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStats => '連線統計';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectionStatsDesc => '檢視伺服器連線成功率和歷史記錄';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get container => '容器';
|
String get container => '容器';
|
||||||
|
|
||||||
@@ -938,6 +1196,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get disconnected => '已中斷連線';
|
String get disconnected => '已中斷連線';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverSshServers => '發現SSH服務器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoveryFailed => '發現失敗';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySettings => '發現設定';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get discoverySummary => '發現摘要';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get disk => '磁碟';
|
String get disk => '磁碟';
|
||||||
|
|
||||||
@@ -989,15 +1259,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get editVirtKeys => '編輯虛擬按鍵';
|
String get editVirtKeys => '編輯虛擬按鍵';
|
||||||
|
|
||||||
@override
|
|
||||||
String get editor => '編輯器';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get editorHighlightTip => '程式碼高亮功能可能影響效能,可選擇性關閉。';
|
String get editorHighlightTip => '程式碼高亮功能可能影響效能,可選擇性關閉。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get emulator => '模擬器';
|
String get emulator => '模擬器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdns => '啟用mDNS';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enableMdnsDesc => '使用mDNS/Bonjour發現SSH服務';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get encode => '編碼';
|
String get encode => '編碼';
|
||||||
|
|
||||||
@@ -1029,10 +1302,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get followSystem => '跟隨系統';
|
String get finishedAt => '完成於';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get font => '字型';
|
String get followSystem => '跟隨系統';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => '字型大小';
|
String get fontSize => '字型大小';
|
||||||
@@ -1064,6 +1337,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get highlight => '程式碼標記';
|
String get highlight => '程式碼標記';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabs => '主頁標籤';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get homeTabsCustomizeDesc => '自訂主頁上顯示的標籤及其順序';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get homeWidgetUrlConfig => '桌面小工具連結配置';
|
String get homeWidgetUrlConfig => '桌面小工具連結配置';
|
||||||
|
|
||||||
@@ -1084,9 +1363,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get imagesList => '映像檔列表';
|
String get imagesList => '映像檔列表';
|
||||||
|
|
||||||
@override
|
|
||||||
String get init => '初始化';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get inner => '內建';
|
String get inner => '內建';
|
||||||
|
|
||||||
@@ -1115,15 +1391,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get keyAuth => '金鑰認證';
|
String get keyAuth => '金鑰認證';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastFailure => '最後失敗';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lastSuccess => '最後成功';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCache => '輸入法字符快取';
|
String get letterCache => '輸入法字符快取';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get letterCacheTip => '建議關閉,但關閉後將無法輸入 CJK 等文字。';
|
String get letterCacheTip => '建議關閉,但關閉後將無法輸入 CJK 等文字。';
|
||||||
|
|
||||||
@override
|
|
||||||
String get license => '憑證';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => '位置';
|
String get location => '位置';
|
||||||
|
|
||||||
@@ -1136,10 +1415,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get manual => '手動';
|
String get max => '最大';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get max => '最大';
|
String get maxConcurrency => '最大並發數';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get maxRetryCount => '伺服器嘗試重連次數';
|
String get maxRetryCount => '伺服器嘗試重連次數';
|
||||||
@@ -1179,6 +1458,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get newContainer => '新建容器';
|
String get newContainer => '新建容器';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noConnectionStatsData => '暫無連線統計資料';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noLineChart => '不使用折線圖';
|
String get noLineChart => '不使用折線圖';
|
||||||
|
|
||||||
@@ -1245,10 +1527,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
String get preferDiskAmount => '優先顯示硬碟容量';
|
String get preferDiskAmount => '優先顯示硬碟容量';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get preview => '預覽';
|
String get privateKey => '私鑰';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privateKey => '私鑰';
|
String privateKeyNotFoundFmt(Object keyId) {
|
||||||
|
return '未找到私鑰 [$keyId]。';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get process => '處理程序';
|
String get process => '處理程序';
|
||||||
@@ -1274,6 +1558,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get reboot => '重開';
|
String get reboot => '重開';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recentConnections => '最近連線記錄';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rememberPwdInMem => '在記憶體中記住密碼';
|
String get rememberPwdInMem => '在記憶體中記住密碼';
|
||||||
|
|
||||||
@@ -1334,6 +1621,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get serverOrder => '伺服器順序';
|
String get serverOrder => '伺服器順序';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get serverTabRequired => '服務器標籤不能被移除';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get servers => '服務器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sftpDlPrepare => '準備連線至伺服器...';
|
String get sftpDlPrepare => '準備連線至伺服器...';
|
||||||
|
|
||||||
@@ -1414,6 +1707,34 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
return '已從SSH設定匯入$count個伺服器';
|
return '已從SSH設定匯入$count個伺服器';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyChangedDesc(Object serverName) {
|
||||||
|
return '伺服器 $serverName 的 SSH 主機金鑰已變更,僅在信任該伺服器時繼續。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
|
||||||
|
return '指紋(MD5 Base64):$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
|
||||||
|
return '指紋(MD5 十六進位):$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sshHostKeyType => 'SSH 主機金鑰類型';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyNewDesc(Object serverName) {
|
||||||
|
return '收到來自 $serverName 的新 SSH 主機金鑰,信任前請先檢查指紋。';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sshHostKeyStoredFingerprint(Object fingerprint) {
|
||||||
|
return '已儲存的指紋:$fingerprint';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sshConfigManualSelect => '是否要手動選擇 SSH 設定檔案?';
|
String get sshConfigManualSelect => '是否要手動選擇 SSH 設定檔案?';
|
||||||
|
|
||||||
@@ -1472,9 +1793,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
return '切換到 $val';
|
return '切換到 $val';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get sync => '同步';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '可能需要重新啟動,某些更改才能生效。';
|
String get syncTip => '可能需要重新啟動,某些更改才能生效。';
|
||||||
|
|
||||||
@@ -1484,6 +1802,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get tag => '標籤';
|
String get tag => '標籤';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapToStartDiscovery => '點擊搜尋按鈕發現網路中的SSH服務器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get temperature => '溫度';
|
String get temperature => '溫度';
|
||||||
|
|
||||||
@@ -1514,6 +1835,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get total => '總共';
|
String get total => '總共';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get totalAttempts => '總次數';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get traffic => '流量';
|
String get traffic => '流量';
|
||||||
|
|
||||||
@@ -1538,9 +1862,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get updateServerStatusInterval => '伺服器狀態更新間隔';
|
String get updateServerStatusInterval => '伺服器狀態更新間隔';
|
||||||
|
|
||||||
@override
|
|
||||||
String get upload => '上傳';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get upsideDown => '上下交換';
|
String get upsideDown => '上下交換';
|
||||||
|
|
||||||
@@ -1565,6 +1886,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get view => '檢視';
|
String get view => '檢視';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewDetails => '檢視詳情';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewErr => '查看錯誤';
|
String get viewErr => '查看錯誤';
|
||||||
|
|
||||||
@@ -1604,4 +1928,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
|||||||
@override
|
@override
|
||||||
String get writeScriptTip =>
|
String get writeScriptTip =>
|
||||||
'連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。';
|
'連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logs => '日誌';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class SpiAdapter extends TypeAdapter<Spi> {
|
|||||||
alterUrl: fields[7] as String?,
|
alterUrl: fields[7] as String?,
|
||||||
autoConnect: fields[8] == null ? true : fields[8] as bool,
|
autoConnect: fields[8] == null ? true : fields[8] as bool,
|
||||||
jumpId: fields[9] as String?,
|
jumpId: fields[9] as String?,
|
||||||
|
jumpChainIds: (fields[16] as List?)?.cast<String>(),
|
||||||
custom: fields[10] as ServerCustom?,
|
custom: fields[10] as ServerCustom?,
|
||||||
wolCfg: fields[11] as WakeOnLanCfg?,
|
wolCfg: fields[11] as WakeOnLanCfg?,
|
||||||
envs: (fields[12] as Map?)?.cast<String, String>(),
|
envs: (fields[12] as Map?)?.cast<String, String>(),
|
||||||
@@ -119,7 +120,7 @@ class SpiAdapter extends TypeAdapter<Spi> {
|
|||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, Spi obj) {
|
void write(BinaryWriter writer, Spi obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(16)
|
..writeByte(17)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.name)
|
..write(obj.name)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
@@ -151,7 +152,9 @@ class SpiAdapter extends TypeAdapter<Spi> {
|
|||||||
..writeByte(14)
|
..writeByte(14)
|
||||||
..write(obj.customSystemType)
|
..write(obj.customSystemType)
|
||||||
..writeByte(15)
|
..writeByte(15)
|
||||||
..write(obj.disabledCmdTypes);
|
..write(obj.disabledCmdTypes)
|
||||||
|
..writeByte(16)
|
||||||
|
..write(obj.jumpChainIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ types:
|
|||||||
index: 4
|
index: 4
|
||||||
Spi:
|
Spi:
|
||||||
typeId: 3
|
typeId: 3
|
||||||
nextIndex: 16
|
nextIndex: 17
|
||||||
fields:
|
fields:
|
||||||
name:
|
name:
|
||||||
index: 0
|
index: 0
|
||||||
@@ -61,6 +61,8 @@ types:
|
|||||||
index: 14
|
index: 14
|
||||||
disabledCmdTypes:
|
disabledCmdTypes:
|
||||||
index: 15
|
index: 15
|
||||||
|
jumpChainIds:
|
||||||
|
index: 16
|
||||||
VirtKey:
|
VirtKey:
|
||||||
typeId: 4
|
typeId: 4
|
||||||
nextIndex: 45
|
nextIndex: 45
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user