mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-02-15 12:44:59 +01:00
Compare commits
19 Commits
lollipopki
...
v1.0.1291
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
874d28be12 | ||
|
|
06070c29b9 | ||
|
|
bb0ada12e6 | ||
|
|
9ceeaf7cc4 | ||
|
|
29a57ad742 | ||
|
|
2c495a44c3 | ||
|
|
cc300c141a | ||
|
|
26efb8e185 | ||
|
|
06ed38ff45 | ||
|
|
7c35abe30e | ||
|
|
78ef181d4a | ||
|
|
3f15caeaf2 | ||
|
|
6458e736fa | ||
|
|
99fda8b747 | ||
|
|
c5cbb12ac3 | ||
|
|
038f0d4d77 | ||
|
|
141519d952 | ||
|
|
75d1a59e77 | ||
|
|
ca4e65d7a5 |
2
.github/workflows/analysis.yml
vendored
2
.github/workflows/analysis.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -17,15 +17,15 @@ permissions:
|
||||
jobs:
|
||||
releaseAndroid:
|
||||
name: Release android
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.35.3"
|
||||
flutter-version: "3.38.0"
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "zulu"
|
||||
@@ -53,10 +53,10 @@ jobs:
|
||||
|
||||
releaseLinux:
|
||||
name: Release linux
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
- name: Install dependencies
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
- name: Build
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
# runs-on: macos-latest
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# uses: actions/checkout@v6
|
||||
# - name: Install Flutter
|
||||
# uses: subosito/flutter-action@v2
|
||||
# - name: Build
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -88,19 +88,19 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||
camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741
|
||||
file_picker: fb04e739ae6239a76ce1f571863a196a922c87d4
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||
icloud_storage: e55639f0c0d7cb2b0ba9c0b3d5968ccca9cd9aa2
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
plain_notification_token: 047876b9d80a5b93565ddcc13a487a7e7b906f7d
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
watch_connectivity: 88e5bea25b473e66ef8d3f960954d154ed0356d6
|
||||
|
||||
|
||||
@@ -748,7 +748,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -758,7 +758,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -884,7 +884,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -894,7 +894,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -912,7 +912,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -922,7 +922,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -943,7 +943,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -956,7 +956,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
@@ -982,7 +982,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -995,7 +995,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1018,7 +1018,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -1031,7 +1031,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1054,7 +1054,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
@@ -1095,7 +1095,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1107,7 +1107,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
@@ -1133,7 +1133,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1145,7 +1145,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
|
||||
23
lib/app.dart
23
lib/app.dart
@@ -25,6 +25,7 @@ class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_setup(context);
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: RNodes.app,
|
||||
builder: (context, _) {
|
||||
@@ -39,6 +40,7 @@ class _MyAppState extends State<MyApp> {
|
||||
|
||||
Widget _build(BuildContext context) {
|
||||
final colorSeed = Color(Stores.setting.colorSeed.fetch());
|
||||
|
||||
UIs.colorSeed = colorSeed;
|
||||
UIs.primaryColor = colorSeed;
|
||||
|
||||
@@ -61,14 +63,31 @@ class _MyAppState extends State<MyApp> {
|
||||
Widget _buildDynamicColor(BuildContext context) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (light, dark) {
|
||||
final lightTheme = ThemeData(useMaterial3: true, colorScheme: light);
|
||||
final darkTheme = ThemeData(useMaterial3: true, brightness: Brightness.dark, colorScheme: dark);
|
||||
final lightSeed = light?.primary;
|
||||
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) {
|
||||
UIs.primaryColor = dark.primary;
|
||||
UIs.colorSeed = dark.primary;
|
||||
} else if (!context.isDark && light != null) {
|
||||
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);
|
||||
|
||||
@@ -35,8 +35,8 @@ abstract final class MethodChans {
|
||||
try {
|
||||
Loggers.app.info('Updating Android sessions: $payload');
|
||||
await _channel.invokeMethod('updateSessions', payload);
|
||||
} catch (_) {
|
||||
// ignore
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to update Android sessions', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ abstract final class MethodChans {
|
||||
try {
|
||||
final res = await _channel.invokeMethod('isServiceRunning');
|
||||
return res == true;
|
||||
} catch (_) {
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to check if Android service is running', e, s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +58,9 @@ abstract final class MethodChans {
|
||||
try {
|
||||
Loggers.app.info('Starting iOS Live Activity: $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 {
|
||||
@@ -65,7 +68,9 @@ abstract final class MethodChans {
|
||||
try {
|
||||
Loggers.app.info('Updating iOS Live Activity: $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 {
|
||||
@@ -73,7 +78,9 @@ abstract final class MethodChans {
|
||||
try {
|
||||
Loggers.app.info('Stopping iOS Live Activity');
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:typed_data';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.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/res/misc.dart';
|
||||
@@ -170,4 +171,98 @@ extension SSHClientX on SSHClient {
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ class SshDiscoveryService {
|
||||
// Some tools return non-zero but still have useful output
|
||||
if (out.trim().isNotEmpty) return out;
|
||||
return null;
|
||||
} catch (_) {
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to run command: $exe ${args.join(' ')}', e, s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -109,7 +110,7 @@ class SshDiscoveryService {
|
||||
}
|
||||
}
|
||||
if (matchCount == 0) {
|
||||
lprint(
|
||||
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}',
|
||||
);
|
||||
}
|
||||
@@ -176,8 +177,7 @@ class SshDiscoveryService {
|
||||
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) {
|
||||
// Log unexpected format but continue processing other lines
|
||||
lprint('[ssh_discovery] Warning: Unexpected ifconfig line format: $line');
|
||||
Loggers.app.warning('[ssh_discovery] Warning: Unexpected ifconfig line format: $line');
|
||||
continue;
|
||||
}
|
||||
final ip = InternetAddress(ipm.group(1)!);
|
||||
@@ -190,7 +190,7 @@ class SshDiscoveryService {
|
||||
final brd = InternetAddress(ipm.group(3) ?? _broadcastAddress(ip, mask).address);
|
||||
res.add(_Cidr(ip, prefix, mask, net, brd));
|
||||
} catch (e) {
|
||||
lprint('[ssh_discovery] Error parsing ifconfig output: $e, line: $line');
|
||||
Loggers.app.warning('[ssh_discovery] Error parsing ifconfig output: $e, line: $line');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,9 @@ class SshDiscoveryService {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
} 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) {
|
||||
@@ -335,7 +337,8 @@ class _Scanner {
|
||||
);
|
||||
final banner = await c.future.timeout(timeout, onTimeout: () => null);
|
||||
return _ScanResult(ip, banner);
|
||||
} catch (_) {
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to probe SSH at ${ip.address}', e, s);
|
||||
return null;
|
||||
} finally {
|
||||
sub?.cancel();
|
||||
|
||||
@@ -26,7 +26,8 @@ final class BakSyncer extends SyncIface {
|
||||
return MergeableUtils.fromJsonString(content, pwd).$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
|
||||
return MergeableUtils.fromJsonString(content).$1;
|
||||
}
|
||||
|
||||
@@ -149,10 +149,12 @@ abstract final class SSHConfig {
|
||||
|
||||
/// Extract jump host from ProxyJump or ProxyCommand
|
||||
static String? _extractJumpHost(String value) {
|
||||
if (value.isEmpty) return null;
|
||||
// For ProxyJump, the format is usually: user@host:port
|
||||
// For ProxyCommand, it's more complex and might need custom parsing
|
||||
if (value.contains('@')) {
|
||||
return value.split(' ').first;
|
||||
final parts = value.split(' ');
|
||||
return parts.isNotEmpty ? parts[0] : 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: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/system.dart';
|
||||
|
||||
@@ -23,7 +24,10 @@ class SystemDetector {
|
||||
|
||||
try {
|
||||
// Try to detect Unix/Linux/BSD systems first (more reliable and doesn't create files)
|
||||
final unixResult = await client.run('uname -a 2>/dev/null').string;
|
||||
final unixResult = await client.runSafe(
|
||||
'uname -a 2>/dev/null',
|
||||
context: 'uname detection for ${spi.oldId}',
|
||||
);
|
||||
if (unixResult.contains('Linux')) {
|
||||
detectedSystemType = SystemType.linux;
|
||||
dprint('Detected Linux system type for ${spi.oldId}');
|
||||
@@ -35,15 +39,19 @@ class SystemDetector {
|
||||
}
|
||||
|
||||
// If uname fails, try to detect Windows systems
|
||||
final powershellResult = await client.run('ver 2>nul').string;
|
||||
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) {
|
||||
Loggers.app.warning('System detection failed for ${spi.oldId}: $e');
|
||||
} catch (e, stackTrace) {
|
||||
Loggers.app.warning('System detection failed for ${spi.oldId}: $e\n$stackTrace');
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
|
||||
@@ -74,8 +74,8 @@ class BackupService {
|
||||
await _confirmAndRestore(context, backup);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// Saved password failed, will prompt for manual input
|
||||
} catch (e, s) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,25 +166,34 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
||||
echo('echo ${SystemType.windowsSign}'),
|
||||
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:
|
||||
/// - Collects bytes received and sent per second for all network interfaces
|
||||
/// Uses WMI Win32_PerfRawData_Tcpip_NetworkInterface for cross-language compatibility:
|
||||
/// - 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(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\NetworkInterface(*)\\Bytes Received/sec", '
|
||||
r'"\\NetworkInterface(*)\\Bytes Sent/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
r'$s1 = @(Get-WmiObject Win32_PerfRawData_Tcpip_NetworkInterface | '
|
||||
r'Select-Object Name, BytesReceivedPersec, BytesSentPersec, Timestamp_Sys100NS); '
|
||||
r'Start-Sleep -Seconds 1; '
|
||||
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'),
|
||||
cpu(
|
||||
'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'),
|
||||
disk(
|
||||
'Get-WmiObject -Class Win32_LogicalDisk | '
|
||||
@@ -213,19 +222,19 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
||||
),
|
||||
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
|
||||
/// - Takes 2 samples with 1 second interval to calculate I/O rates
|
||||
/// - Physical disk counters provide hardware-level I/O statistics
|
||||
/// - Outputs results in JSON format for parsing
|
||||
/// - Counter names use wildcard (*) to capture all disk instances
|
||||
/// - Takes 2 samples with 1 second interval to calculate rates
|
||||
/// - DiskReadBytesPersec and DiskWriteBytesPersec are cumulative counters
|
||||
diskio(
|
||||
r'Get-Counter -Counter '
|
||||
r'"\\PhysicalDisk(*)\\Disk Read Bytes/sec", '
|
||||
r'"\\PhysicalDisk(*)\\Disk Write Bytes/sec" '
|
||||
r'-SampleInterval 1 -MaxSamples 2 | ConvertTo-Json',
|
||||
r'$s1 = @(Get-WmiObject Win32_PerfRawData_PerfDisk_PhysicalDisk | '
|
||||
r'Select-Object Name, DiskReadBytesPersec, DiskWriteBytesPersec, Timestamp_Sys100NS); '
|
||||
r'Start-Sleep -Seconds 1; '
|
||||
r'$s2 = @(Get-WmiObject Win32_PerfRawData_PerfDisk_PhysicalDisk | '
|
||||
r'Select-Object Name, DiskReadBytesPersec, DiskWriteBytesPersec, Timestamp_Sys100NS); '
|
||||
r'@($s1, $s2) | ConvertTo-Json -Depth 5',
|
||||
),
|
||||
battery(
|
||||
'Get-WmiObject -Class Win32_Battery | '
|
||||
@@ -287,7 +296,7 @@ enum WindowsStatusCmdType implements ShellCmdType {
|
||||
String get separator => ScriptConstants.getCmdSeparator(name);
|
||||
|
||||
@override
|
||||
String get divider => ScriptConstants.getCmdDivider(name);
|
||||
String get divider => ScriptConstants.getWindowsCmdDivider(name);
|
||||
|
||||
@override
|
||||
CmdTypeSys get sysType => CmdTypeSys.windows;
|
||||
|
||||
@@ -29,6 +29,9 @@ class ScriptConstants {
|
||||
/// Generate command-specific divider
|
||||
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
|
||||
static Map<String, String> parseScriptOutput(String raw) {
|
||||
final result = <String, String>{};
|
||||
@@ -102,6 +105,7 @@ exec 2>/dev/null
|
||||
# DO NOT delete this file while app is running
|
||||
|
||||
\$ErrorActionPreference = "SilentlyContinue"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
''';
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ sealed class ContainerPs {
|
||||
|
||||
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 {
|
||||
@@ -55,7 +55,7 @@ final class PodmanPs implements ContainerPs {
|
||||
ContainerStatus get status => ContainerStatus.fromPodmanExited(exited);
|
||||
|
||||
@override
|
||||
void parseStats(String s) {
|
||||
void parseStats(String s, [String? version]) {
|
||||
final stats = json.decode(s);
|
||||
final cpuD = (stats['CPU'] as double? ?? 0).toStringAsFixed(1);
|
||||
final cpuAvgD = (stats['AvgCPU'] as double? ?? 0).toStringAsFixed(1);
|
||||
@@ -63,12 +63,32 @@ final class PodmanPs implements ContainerPs {
|
||||
final memLimit = (stats['MemLimit'] as int? ?? 0).bytes2Str;
|
||||
final memUsage = (stats['MemUsage'] as int? ?? 0).bytes2Str;
|
||||
mem = '$memUsage / $memLimit';
|
||||
final netIn = (stats['NetInput'] as int? ?? 0).bytes2Str;
|
||||
final netOut = (stats['NetOutput'] as int? ?? 0).bytes2Str;
|
||||
net = '↓ $netIn / ↑ $netOut';
|
||||
|
||||
int netIn = 0;
|
||||
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 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));
|
||||
@@ -125,12 +145,18 @@ final class DockerPs implements ContainerPs {
|
||||
ContainerStatus get status => ContainerStatus.fromDockerState(state);
|
||||
|
||||
@override
|
||||
void parseStats(String s) {
|
||||
void parseStats(String s, [String? version]) {
|
||||
final stats = json.decode(s);
|
||||
cpu = stats['CPUPerc'];
|
||||
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
|
||||
|
||||
@@ -62,6 +62,7 @@ class UpgradePkgInfo {
|
||||
|
||||
void _parsePacman(String raw) {
|
||||
final parts = raw.split(' ');
|
||||
if (parts.length < 4) throw Exception('Invalid pacman output format');
|
||||
package = parts[0];
|
||||
nowVersion = parts[1];
|
||||
newVersion = parts[3];
|
||||
@@ -70,6 +71,7 @@ class UpgradePkgInfo {
|
||||
|
||||
void _parseOpkg(String raw) {
|
||||
final parts = raw.split(' - ');
|
||||
if (parts.length < 3) throw Exception('Invalid opkg output format');
|
||||
package = parts[0];
|
||||
nowVersion = parts[1];
|
||||
newVersion = parts[2];
|
||||
@@ -80,6 +82,7 @@ class UpgradePkgInfo {
|
||||
void _parseApk(String raw) {
|
||||
final parts = raw.split(' ');
|
||||
final len = parts.length;
|
||||
if (len < 2) throw Exception('Invalid apk output format');
|
||||
newVersion = parts[len - 1];
|
||||
nowVersion = parts[0];
|
||||
newVersion = newVersion.substring(0, newVersion.length - 1);
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:server_box/data/res/status.dart';
|
||||
/// Capacity of the FIFO queue
|
||||
const _kCap = 30;
|
||||
|
||||
class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
class Cpus extends TimeSeq<SingleCpuCore> {
|
||||
Cpus(super.init1, super.init2);
|
||||
|
||||
final Map<String, int> brand = {};
|
||||
@@ -14,21 +14,30 @@ class Cpus extends TimeSeq<List<SingleCpuCore>> {
|
||||
@override
|
||||
void onUpdate() {
|
||||
_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;
|
||||
_user = _getUser();
|
||||
_sys = _getSys();
|
||||
_iowait = _getIowait();
|
||||
_idle = _getIdle();
|
||||
_updateSpots();
|
||||
//_updateRange();
|
||||
}
|
||||
|
||||
double usedPercent({int coreIdx = 0}) {
|
||||
if (now.length != pre.length) return 0;
|
||||
if (now.isEmpty) return 0;
|
||||
if (coreIdx >= now.length) return 0;
|
||||
try {
|
||||
final idleDelta = now[coreIdx].idle - pre[coreIdx].idle;
|
||||
final totalDelta = now[coreIdx].total - pre[coreIdx].total;
|
||||
if (totalDelta == 0) return 0;
|
||||
final used = idleDelta / totalDelta;
|
||||
return used.isNaN ? 0 : 100 - used * 100;
|
||||
} catch (e, s) {
|
||||
@@ -157,6 +166,7 @@ class SingleCpuCore extends TimeSeqIface<SingleCpuCore> {
|
||||
final id = item.split(' ').firstOrNull;
|
||||
if (id == null) continue;
|
||||
final matches = item.replaceFirst(id, '').trim().split(' ');
|
||||
if (matches.length < 7) continue;
|
||||
cpus.add(
|
||||
SingleCpuCore(
|
||||
id,
|
||||
|
||||
@@ -280,7 +280,7 @@ class Disk with EquatableMixin {
|
||||
];
|
||||
}
|
||||
|
||||
class DiskIO extends TimeSeq<List<DiskIOPiece>> {
|
||||
class DiskIO extends TimeSeq<DiskIOPiece> {
|
||||
DiskIO(super.init1, super.init2);
|
||||
|
||||
@override
|
||||
|
||||
@@ -18,7 +18,7 @@ class NetSpeedPart extends TimeSeqIface<NetSpeedPart> {
|
||||
|
||||
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);
|
||||
|
||||
@override
|
||||
@@ -164,7 +164,8 @@ class NetSpeed extends TimeSeq<List<NetSpeedPart>> {
|
||||
final bytesIn = BigInt.parse(bytes.first);
|
||||
final bytesOut = BigInt.parse(bytes[8]);
|
||||
results.add(NetSpeedPart(device, bytesIn, bytesOut, time));
|
||||
} catch (_) {
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to parse net speed data: $item', e, s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ class Proc {
|
||||
}
|
||||
|
||||
String get binary {
|
||||
final parts = command.split(' ');
|
||||
return parts[0];
|
||||
final parts = command.trim().split(' ').where((e) => e.isNotEmpty).toList();
|
||||
return parts.isNotEmpty ? parts[0] : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -378,18 +378,27 @@ void _parseWindowsCpuData(ServerStatusUpdateReq req, Map<String, String> parsedO
|
||||
// Windows CPU parsing - JSON format from PowerShell
|
||||
final cpuRaw = WindowsStatusCmdType.cpu.findInMap(parsedOutput);
|
||||
if (cpuRaw.isNotEmpty && cpuRaw != 'null' && !cpuRaw.contains('error') && !cpuRaw.contains('Exception')) {
|
||||
final cpus = WindowsParser.parseCpu(cpuRaw, req.ss);
|
||||
if (cpus.isNotEmpty) {
|
||||
req.ss.cpu.update(cpus);
|
||||
final cpuResult = WindowsParser.parseCpu(cpuRaw, req.ss);
|
||||
if (cpuResult.cores.isNotEmpty) {
|
||||
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) {
|
||||
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
|
||||
void _parseWindowsUptimeData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final uptime = WindowsParser.parseUpTime(WindowsStatusCmdType.uptime.findInMap(parsedOutput));
|
||||
if (uptime != null) {
|
||||
final uptimeRaw = WindowsStatusCmdType.uptime.findInMap(parsedOutput);
|
||||
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;
|
||||
}
|
||||
} catch (e, s) {
|
||||
@@ -541,38 +553,36 @@ List<NetSpeedPart> _parseWindowsNetwork(String raw, int currentTime) {
|
||||
final dynamic jsonData = json.decode(raw);
|
||||
final List<NetSpeedPart> netParts = [];
|
||||
|
||||
// PowerShell Get-Counter returns a structure with CounterSamples
|
||||
if (jsonData is Map && jsonData.containsKey('CounterSamples')) {
|
||||
final samples = jsonData['CounterSamples'] as List?;
|
||||
if (samples != null && samples.length >= 2) {
|
||||
// We need 2 samples to calculate speed (interval between them)
|
||||
final Map<String, double> interfaceRx = {};
|
||||
final Map<String, double> interfaceTx = {};
|
||||
|
||||
for (final sample in samples) {
|
||||
final path = sample['Path']?.toString() ?? '';
|
||||
final cookedValue = sample['CookedValue'] as num? ?? 0;
|
||||
|
||||
if (path.contains('Bytes Received/sec')) {
|
||||
final interfaceName = _extractInterfaceName(path);
|
||||
if (interfaceName.isNotEmpty) {
|
||||
interfaceRx[interfaceName] = cookedValue.toDouble();
|
||||
}
|
||||
} else if (path.contains('Bytes Sent/sec')) {
|
||||
final interfaceName = _extractInterfaceName(path);
|
||||
if (interfaceName.isNotEmpty) {
|
||||
interfaceTx[interfaceName] = cookedValue.toDouble();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create NetSpeedPart for each interface
|
||||
for (final interfaceName in interfaceRx.keys) {
|
||||
final rx = interfaceRx[interfaceName] ?? 0;
|
||||
final tx = interfaceTx[interfaceName] ?? 0;
|
||||
|
||||
if (jsonData is List && jsonData.length >= 2) {
|
||||
var sample1 = jsonData[jsonData.length - 2];
|
||||
var sample2 = jsonData[jsonData.length - 1];
|
||||
if (sample1 is Map && sample1.containsKey('value')) {
|
||||
sample1 = sample1['value'];
|
||||
}
|
||||
if (sample2 is Map && sample2.containsKey('value')) {
|
||||
sample2 = sample2['value'];
|
||||
}
|
||||
if (sample1 is List && sample2 is List && sample1.length == sample2.length) {
|
||||
for (int i = 0; i < sample1.length; i++) {
|
||||
final s1 = sample1[i];
|
||||
final s2 = sample2[i];
|
||||
final name = s1['Name']?.toString() ?? '';
|
||||
if (name.isEmpty || name == '_Total') continue;
|
||||
final rx1 = (s1['BytesReceivedPersec'] as num?)?.toDouble() ?? 0;
|
||||
final rx2 = (s2['BytesReceivedPersec'] as num?)?.toDouble() ?? 0;
|
||||
final tx1 = (s1['BytesSentPersec'] as num?)?.toDouble() ?? 0;
|
||||
final tx2 = (s2['BytesSentPersec'] as num?)?.toDouble() ?? 0;
|
||||
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 rxDelta = rx2 - rx1;
|
||||
final txDelta = tx2 - tx1;
|
||||
if (rxDelta < 0 || txDelta < 0) continue;
|
||||
final rxSpeed = rxDelta / timeDelta;
|
||||
final txSpeed = txDelta / timeDelta;
|
||||
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) {
|
||||
try {
|
||||
final dynamic jsonData = json.decode(raw);
|
||||
final List<DiskIOPiece> diskParts = [];
|
||||
|
||||
// PowerShell Get-Counter returns a structure with CounterSamples
|
||||
if (jsonData is Map && jsonData.containsKey('CounterSamples')) {
|
||||
final samples = jsonData['CounterSamples'] as List?;
|
||||
if (samples != null) {
|
||||
final Map<String, double> diskReads = {};
|
||||
final Map<String, double> diskWrites = {};
|
||||
|
||||
for (final sample in samples) {
|
||||
final path = sample['Path']?.toString() ?? '';
|
||||
final cookedValue = sample['CookedValue'] as num? ?? 0;
|
||||
|
||||
if (path.contains('Disk Read Bytes/sec')) {
|
||||
final diskName = _extractDiskName(path);
|
||||
if (diskName.isNotEmpty) {
|
||||
diskReads[diskName] = cookedValue.toDouble();
|
||||
}
|
||||
} else if (path.contains('Disk Write Bytes/sec')) {
|
||||
final diskName = _extractDiskName(path);
|
||||
if (diskName.isNotEmpty) {
|
||||
diskWrites[diskName] = cookedValue.toDouble();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create DiskIOPiece for each disk - convert bytes to sectors
|
||||
// (assuming 512 bytes per sector)
|
||||
for (final diskName in diskReads.keys) {
|
||||
final readBytes = diskReads[diskName] ?? 0;
|
||||
final writeBytes = diskWrites[diskName] ?? 0;
|
||||
final sectorsRead = (readBytes / 512).round();
|
||||
final sectorsWrite = (writeBytes / 512).round();
|
||||
if (jsonData is List && jsonData.length >= 2) {
|
||||
var sample1 = jsonData[jsonData.length - 2];
|
||||
var sample2 = jsonData[jsonData.length - 1];
|
||||
if (sample1 is Map && sample1.containsKey('value')) {
|
||||
sample1 = sample1['value'];
|
||||
}
|
||||
if (sample2 is Map && sample2.containsKey('value')) {
|
||||
sample2 = sample2['value'];
|
||||
}
|
||||
if (sample1 is List && sample2 is List && sample1.length == sample2.length) {
|
||||
for (int i = 0; i < sample1.length; i++) {
|
||||
final s1 = sample1[i];
|
||||
final s2 = sample2[i];
|
||||
final name = s1['Name']?.toString() ?? '';
|
||||
if (name.isEmpty || name == '_Total') continue;
|
||||
final read1 = (s1['DiskReadBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||
final read2 = (s2['DiskReadBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||
final write1 = (s1['DiskWriteBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||
final write2 = (s2['DiskWriteBytesPersec'] as num?)?.toDouble() ?? 0;
|
||||
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;
|
||||
final writeDelta = write2 - write1;
|
||||
if (readDelta < 0 || writeDelta < 0) continue;
|
||||
final readSpeed = readDelta / timeDelta;
|
||||
final writeSpeed = writeDelta / timeDelta;
|
||||
final sectorsRead = (readSpeed / 512).round();
|
||||
final sectorsWrite = (writeSpeed / 512).round();
|
||||
|
||||
diskParts.add(
|
||||
DiskIOPiece(
|
||||
dev: diskName,
|
||||
dev: name,
|
||||
sectorsRead: sectorsRead,
|
||||
sectorsWrite: sectorsWrite,
|
||||
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) {
|
||||
try {
|
||||
// Handle error output
|
||||
@@ -684,7 +679,7 @@ void _parseWindowsTemperatures(Temperatures temps, String raw) {
|
||||
if (typeLines.isNotEmpty && valueLines.isNotEmpty) {
|
||||
temps.parse(typeLines.join('\n'), valueLines.join('\n'));
|
||||
}
|
||||
} catch (e) {
|
||||
// If JSON parsing fails, ignore temperature data
|
||||
} catch (e, s) {
|
||||
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] /
|
||||
/// [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];
|
||||
}
|
||||
|
||||
T get now {
|
||||
List<T> get now {
|
||||
return _list[length - 1];
|
||||
}
|
||||
|
||||
void onUpdate();
|
||||
|
||||
void update(T new_) {
|
||||
void update(List<T> new_) {
|
||||
add(new_);
|
||||
|
||||
if (pre.length != now.length) {
|
||||
pre.removeWhere((e) => now.any((el) => e.same(el)));
|
||||
pre.addAll(now.where((e) => pre.every((el) => !e.same(el))));
|
||||
final previous = pre.toList(growable: false);
|
||||
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();
|
||||
|
||||
@@ -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/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
|
||||
///
|
||||
/// This module handles parsing of Windows PowerShell command outputs
|
||||
@@ -94,30 +101,75 @@ class WindowsParser {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
final dynamic jsonData = json.decode(raw);
|
||||
final List<SingleCpuCore> cpus = [];
|
||||
int totalCoreCount = 1;
|
||||
|
||||
if (jsonData is List) {
|
||||
for (int i = 0; i < jsonData.length; i++) {
|
||||
final cpu = jsonData[i];
|
||||
final loadPercentage = cpu['LoadPercentage'] ?? 0;
|
||||
final usage = loadPercentage as int;
|
||||
// Multiple physical processors
|
||||
totalCoreCount = 0; // Reset to sum up
|
||||
var logicalProcessorOffset = 0;
|
||||
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;
|
||||
|
||||
// Get previous CPU data to calculate cumulative values
|
||||
final prevCpus = serverStatus.cpu.now;
|
||||
final prevCpu = i < prevCpus.length ? prevCpus[i] : null;
|
||||
// Create a SingleCpuCore entry for each logical processor
|
||||
// Windows only reports overall CPU load, so we distribute it evenly
|
||||
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
|
||||
// PowerShell provides LoadPercentage as instantaneous percentage, not cumulative time.
|
||||
// We simulate cumulative counters by adding current percentages to previous totals.
|
||||
// This approach has limitations:
|
||||
// 1. Not as accurate as true cumulative time counters (Linux /proc/stat)
|
||||
// 2. May drift over time with variable polling intervals
|
||||
// 3. Results depend on consistent polling frequency
|
||||
// However, this allows compatibility with existing delta-based CPU calculation logic.
|
||||
// LIMITATION: Windows CPU counters approach
|
||||
// PowerShell provides LoadPercentage as instantaneous percentage, not cumulative time.
|
||||
// We simulate cumulative counters by adding current percentages to previous totals.
|
||||
// Additionally, Windows only provides overall CPU load, not per-core load.
|
||||
// We distribute the load evenly across all logical processors.
|
||||
final newUser = (prevCpu?.user ?? 0) + usage;
|
||||
final newIdle = (prevCpu?.idle ?? 0) + idle;
|
||||
|
||||
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 newIdle = (prevCpu?.idle ?? 0) + idle;
|
||||
|
||||
@@ -125,46 +177,43 @@ class WindowsParser {
|
||||
SingleCpuCore(
|
||||
'cpu$i',
|
||||
newUser, // cumulative user time
|
||||
0, // sys (not available)
|
||||
0, // nice (not available)
|
||||
0, // sys
|
||||
0, // nice
|
||||
newIdle, // cumulative idle time
|
||||
0, // iowait (not available)
|
||||
0, // irq (not available)
|
||||
0, // softirq (not available)
|
||||
0, // iowait
|
||||
0, // irq
|
||||
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;
|
||||
} catch (e) {
|
||||
return [];
|
||||
// Add a summary entry at the beginning (like Linux 'cpu' line)
|
||||
// This is the aggregate of all logical processors
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class SftpReq {
|
||||
String? privateKey;
|
||||
Spi? jumpSpi;
|
||||
String? jumpPrivateKey;
|
||||
Map<String, String>? knownHostFingerprints;
|
||||
|
||||
SftpReq(this.spi, this.remotePath, this.localPath, this.type) {
|
||||
final keyId = spi.keyId;
|
||||
@@ -18,6 +19,12 @@ class SftpReq {
|
||||
jumpSpi = Stores.server.box.get(spi.jumpId);
|
||||
jumpPrivateKey = Stores.key.fetchOne(jumpSpi?.keyId)?.key;
|
||||
}
|
||||
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 +37,7 @@ class SftpReqStatus {
|
||||
late SftpWorker worker;
|
||||
final Completer? completer;
|
||||
|
||||
String get fileName => req.localPath.split('/').last;
|
||||
String get fileName => req.localPath.split(Pfs.seperator).last;
|
||||
|
||||
// status of the download
|
||||
double? progress;
|
||||
|
||||
@@ -65,11 +65,12 @@ Future<void> _download(SftpReq req, SendPort mainSendPort, SendErrorFunction sen
|
||||
privateKey: req.privateKey,
|
||||
jumpSpi: req.jumpSpi,
|
||||
jumpPrivateKey: req.jumpPrivateKey,
|
||||
knownHostFingerprints: req.knownHostFingerprints,
|
||||
);
|
||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Use [FileMode.write] to overwrite the file
|
||||
@@ -121,6 +122,7 @@ Future<void> _upload(SftpReq req, SendPort mainSendPort, SendErrorFunction sendE
|
||||
privateKey: req.privateKey,
|
||||
jumpSpi: req.jumpSpi,
|
||||
jumpPrivateKey: req.jumpPrivateKey,
|
||||
knownHostFingerprints: req.knownHostFingerprints,
|
||||
);
|
||||
mainSendPort.send(SftpWorkerStatus.sshConnectted);
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ class ContainerNotifier extends _$ContainerNotifier {
|
||||
(element) => element.contains(id.substring(0, 5)),
|
||||
);
|
||||
if (statsLine == null) continue;
|
||||
item.parseStats(statsLine);
|
||||
item.parseStats(statsLine, state.version);
|
||||
}
|
||||
} catch (e, trace) {
|
||||
state = state.copyWith(
|
||||
@@ -280,7 +280,7 @@ enum ContainerCmdType {
|
||||
return switch (this) {
|
||||
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
||||
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.
|
||||
ContainerType.docker =>
|
||||
'$prefix ps -a --format "table {{printf \\"'
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
@@ -108,7 +107,7 @@ class PveNotifier extends _$PveNotifier {
|
||||
final newUrl = Uri.parse(
|
||||
addr,
|
||||
).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 {
|
||||
try {
|
||||
await _serverSocket.close();
|
||||
} catch (_) {}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to close server socket', e, s);
|
||||
}
|
||||
for (final forward in _forwards) {
|
||||
try {
|
||||
forward.close();
|
||||
} catch (_) {}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Failed to close forward', e, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter_gbk2utf8/flutter_gbk2utf8.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:server_box/core/extension/ssh_client.dart';
|
||||
import 'package:server_box/core/utils/server.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/model/app/error.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
@@ -213,7 +212,9 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
final newStatus = state.status..system = detectedSystemType;
|
||||
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 {
|
||||
final scriptRaw = ShellFuncManager.allScript(
|
||||
spi.custom?.cmds,
|
||||
@@ -228,10 +229,22 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
systemType: detectedSystemType,
|
||||
customDir: spi.custom?.scriptDir,
|
||||
),
|
||||
systemType: detectedSystemType,
|
||||
context: 'WriteScript<${spi.name}>',
|
||||
);
|
||||
if (writeScriptResult.isNotEmpty && detectedSystemType != SystemType.windows) {
|
||||
ShellFuncManager.switchScriptDir(spi.id, systemType: detectedSystemType);
|
||||
throw writeScriptResult;
|
||||
|
||||
if (stdoutResult.isNotEmpty) {
|
||||
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) {
|
||||
TryLimiter.inc(sid);
|
||||
@@ -278,43 +291,25 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
String? raw;
|
||||
|
||||
try {
|
||||
final execResult = await state.client?.run(
|
||||
ShellFunc.status.exec(spi.id, systemType: state.status.system, customDir: spi.custom?.scriptDir),
|
||||
);
|
||||
final statusCmd = 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) {
|
||||
String? rawStr;
|
||||
bool needGbk = false;
|
||||
try {
|
||||
rawStr = utf8.decode(execResult, allowMalformed: true);
|
||||
// If there are unparseable characters, try fallback to GBK decoding
|
||||
if (rawStr.contains('<EFBFBD>')) {
|
||||
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;
|
||||
raw = SSHDecoder.decode(
|
||||
execResult,
|
||||
isWindows: state.status.system == SystemType.windows,
|
||||
context: 'GetStatus<${spi.name}>',
|
||||
);
|
||||
// Loggers.app.info('Status response length for ${spi.name}: ${raw.length} bytes');
|
||||
} 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);
|
||||
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);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
@@ -324,7 +319,7 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
}
|
||||
|
||||
segments = raw.split(ScriptConstants.separator).map((e) => e.trim()).toList();
|
||||
if (raw.isEmpty || segments.isEmpty) {
|
||||
if (segments.isEmpty) {
|
||||
if (Stores.setting.keepStatusWhenErr.fetch()) {
|
||||
// Keep previous server status when error occurs
|
||||
if (state.conn != ServerConn.failed && state.status.more.isNotEmpty) {
|
||||
@@ -333,7 +328,7 @@ class ServerNotifier extends _$ServerNotifier {
|
||||
}
|
||||
TryLimiter.inc(sid);
|
||||
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);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
|
||||
abstract class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 1270;
|
||||
static const int script = 69;
|
||||
static const int build = 1291;
|
||||
static const int script = 70;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ abstract final class GithubIds {
|
||||
'MasedMSD',
|
||||
'GitGitro',
|
||||
'Shin-suechtig',
|
||||
'GT-610'
|
||||
};
|
||||
|
||||
static const participants = <GhId>{
|
||||
|
||||
@@ -1885,6 +1885,54 @@ abstract class AppLocalizations {
|
||||
/// 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.'**
|
||||
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
|
||||
|
||||
@@ -1007,4 +1007,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -998,4 +998,28 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -1009,4 +1009,28 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -1012,4 +1012,28 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -998,4 +998,28 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -967,5 +967,29 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
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 => 'ログ';
|
||||
}
|
||||
|
||||
@@ -1005,4 +1005,28 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -1000,4 +1000,28 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -1004,4 +1004,28 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get writeScriptTip =>
|
||||
'После подключения к серверу скрипт будет записан в `~/.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 => 'Журналы';
|
||||
}
|
||||
|
||||
@@ -999,4 +999,28 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
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.';
|
||||
|
||||
@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';
|
||||
}
|
||||
|
||||
@@ -1004,4 +1004,28 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get writeScriptTip =>
|
||||
'Після підключення до сервера скрипт буде записано у `~/.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 => 'Журнали';
|
||||
}
|
||||
|
||||
@@ -953,6 +953,30 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get writeScriptTip =>
|
||||
'在连接服务器后,会向 `~/.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`).
|
||||
@@ -1904,4 +1928,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
||||
@override
|
||||
String get writeScriptTip =>
|
||||
'連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。';
|
||||
|
||||
@override
|
||||
String get logs => '日誌';
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.",
|
||||
"write": "Schreiben",
|
||||
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Protokolle"
|
||||
}
|
||||
|
||||
@@ -296,5 +296,13 @@
|
||||
"wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.",
|
||||
"write": "Write",
|
||||
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
|
||||
"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."
|
||||
"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.",
|
||||
"menuSettings": "Setting",
|
||||
"menuQuit": "Quit",
|
||||
"menuNavigate": "Navigate",
|
||||
"menuInfo": "Info",
|
||||
"menuGitHubRepository": "GitHub Repository",
|
||||
"menuWiki": "Wiki",
|
||||
"menuHelp": "Help",
|
||||
"logs": "Logs"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.",
|
||||
"write": "Escribir",
|
||||
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Registros"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.",
|
||||
"write": "Écrire",
|
||||
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Journaux"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.",
|
||||
"write": "Tulis",
|
||||
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Log"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "WOL(Wake-on-LAN)を設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
|
||||
"write": "書き込み",
|
||||
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
|
||||
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。"
|
||||
"writeScriptTip": "サーバーへの接続後、システムステータスを監視するスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。",
|
||||
"logs": "ログ"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.",
|
||||
"write": "Schrijven",
|
||||
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Logboeken"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.",
|
||||
"write": "Escrita",
|
||||
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Logs"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.",
|
||||
"write": "Запись",
|
||||
"writeScriptFailTip": "Запись скрипта не удалась, возможно, из-за отсутствия прав или потому что, директории не существует.",
|
||||
"writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта."
|
||||
"writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.",
|
||||
"logs": "Журналы"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "WOL (Wake-on-LAN) yapılandırıldıktan sonra, sunucuya her bağlanıldığında bir WOL isteği gönderilir.",
|
||||
"write": "Yaz",
|
||||
"writeScriptFailTip": "Betik yazma başarısız oldu, muhtemelen izin eksikliği veya dizin mevcut değil.",
|
||||
"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."
|
||||
"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.",
|
||||
"logs": "Günlükler"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "Після налаштування WOL (Wake-on-LAN), при кожному підключенні до сервера відправляється запит WOL.",
|
||||
"write": "Записати",
|
||||
"writeScriptFailTip": "Запис у скрипт не вдався, можливо, через брак дозволів або каталог не існує.",
|
||||
"writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта."
|
||||
"writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.",
|
||||
"logs": "Журнали"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,13 @@
|
||||
"wolTip": "配置 WOL 后,每次连接服务器时将自动发送唤醒请求",
|
||||
"write": "写",
|
||||
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
|
||||
"writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。"
|
||||
"writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。",
|
||||
"menuSettings": "设置",
|
||||
"menuQuit": "退出",
|
||||
"menuNavigate": "导航",
|
||||
"menuInfo": "信息",
|
||||
"menuGitHubRepository": "GitHub 仓库",
|
||||
"menuWiki": "Wiki",
|
||||
"menuHelp": "帮助",
|
||||
"logs": "日志"
|
||||
}
|
||||
|
||||
@@ -293,5 +293,6 @@
|
||||
"wolTip": "設定 WOL 後,每次連線伺服器時將自動發送喚醒請求",
|
||||
"write": "寫入",
|
||||
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
|
||||
"writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。"
|
||||
"writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。",
|
||||
"logs": "日誌"
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ void _setupDebug() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
DebugProvider.addLog(record);
|
||||
lprint(record);
|
||||
if (record.error != null) print(record.error);
|
||||
if (record.stackTrace != null) print(record.stackTrace);
|
||||
});
|
||||
|
||||
@@ -219,7 +219,7 @@ extension on _ContainerPageState {
|
||||
'${switch (_containerState.type) {
|
||||
ContainerType.podman => 'podman',
|
||||
ContainerType.docker => 'docker',
|
||||
}} exec -it ${dItem.id} sh',
|
||||
}} exec -it ${dItem.id} sh -c "command -v bash && exec bash || command -v ash && exec ash || exec sh"',
|
||||
);
|
||||
SSHPage.route.go(context, args);
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/foundation.dart' show kReleaseMode;
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -5,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:server_box/core/chan.dart';
|
||||
import 'package:server_box/core/sync.dart';
|
||||
import 'package:server_box/data/model/app/menu/platform.dart';
|
||||
import 'package:server_box/data/model/app/tab.dart';
|
||||
import 'package:server_box/data/provider/server/all.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
@@ -134,7 +137,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
super.build(context);
|
||||
final isMobile = ResponsiveBreakpoints.of(context).isMobile;
|
||||
|
||||
return Scaffold(
|
||||
final Widget mainContent = Scaffold(
|
||||
appBar: _AppBar(MediaQuery.paddingOf(context).top),
|
||||
body: Row(
|
||||
children: [
|
||||
@@ -157,6 +160,16 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
),
|
||||
bottomNavigationBar: isMobile ? _buildBottomBar() : null,
|
||||
);
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
return PlatformMenuBar(
|
||||
menus: MacOSMenuBarManager.buildMenuBar(context, (int index) {
|
||||
_onDestinationSelected(index);
|
||||
}),
|
||||
child: mainContent,
|
||||
);
|
||||
}
|
||||
return mainContent;
|
||||
}
|
||||
|
||||
Widget _buildBottomBar() {
|
||||
|
||||
@@ -346,6 +346,6 @@ class _ServerPageState extends ConsumerState<ServerPage>
|
||||
|
||||
static const _kCardHeightMin = 23.0;
|
||||
static const _kCardHeightFlip = 99.0;
|
||||
static const _kCardHeightNormal = 108.0;
|
||||
static const _kCardHeightNormal = 110.0;
|
||||
static const _kCardHeightMoveOutFuncs = 135.0;
|
||||
}
|
||||
|
||||
@@ -94,19 +94,33 @@ extension _App on _AppSettingsPageState {
|
||||
}),
|
||||
onTap: () {
|
||||
withTextFieldController((ctrl) async {
|
||||
ctrl.text = Color(_setting.colorSeed.fetch()).toHex;
|
||||
await context.showRoundDialog(
|
||||
title: libL10n.primaryColorSeed,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
final children = <Widget>[
|
||||
/// Plugin [dynamic_color] is not supported on iOS
|
||||
if (!isIOS)
|
||||
ListTile(
|
||||
title: Text(l10n.followSystem),
|
||||
trailing: StoreSwitch(
|
||||
prop: _setting.useSystemPrimaryColor,
|
||||
callback: (_) => setState(() {}),
|
||||
),
|
||||
DynamicColorBuilder(
|
||||
builder: (light, dark) {
|
||||
final supported = light != null || dark != null;
|
||||
if (!supported) {
|
||||
if (!_setting.useSystemPrimaryColor.fetch()) {
|
||||
_setting.useSystemPrimaryColor.put(false);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return ListTile(
|
||||
title: Text(l10n.followSystem),
|
||||
trailing: StoreSwitch(
|
||||
prop: _setting.useSystemPrimaryColor,
|
||||
callback: (_) => setState(() {}),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
if (!_setting.useSystemPrimaryColor.fetch()) {
|
||||
@@ -129,12 +143,22 @@ extension _App on _AppSettingsPageState {
|
||||
|
||||
void _onSaveColor(String s) {
|
||||
final color = s.fromColorHex;
|
||||
|
||||
if (color == null) {
|
||||
context.showSnackBar(libL10n.fail);
|
||||
return;
|
||||
}
|
||||
UIs.colorSeed = color;
|
||||
|
||||
// Save the color seed to settings
|
||||
_setting.colorSeed.put(color.value255);
|
||||
|
||||
// Only update UIs colors if we're not in system mode
|
||||
if (!_setting.useSystemPrimaryColor.fetch()) {
|
||||
UIs.primaryColor = color;
|
||||
UIs.colorSeed = color;
|
||||
}
|
||||
|
||||
RNodes.app.notify();
|
||||
context.pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
@@ -28,6 +29,7 @@ import 'package:server_box/view/page/setting/seq/srv_seq.dart';
|
||||
import 'package:server_box/view/page/setting/seq/virt_key.dart';
|
||||
|
||||
part 'about.dart';
|
||||
part 'entries/ai.dart';
|
||||
part 'entries/app.dart';
|
||||
part 'entries/container.dart';
|
||||
part 'entries/editor.dart';
|
||||
@@ -35,7 +37,6 @@ part 'entries/full_screen.dart';
|
||||
part 'entries/server.dart';
|
||||
part 'entries/sftp.dart';
|
||||
part 'entries/ssh.dart';
|
||||
part 'entries/ai.dart';
|
||||
|
||||
const _kIconSize = 23.0;
|
||||
|
||||
@@ -71,9 +72,9 @@ class _SettingsPageState extends ConsumerState<SettingsPage> with SingleTickerPr
|
||||
),
|
||||
actions: [
|
||||
Btn.text(
|
||||
text: 'Logs',
|
||||
text: context.l10n.logs,
|
||||
onTap: () =>
|
||||
DebugPage.route.go(context, args: const DebugPageArgs(title: 'Logs(${BuildData.build})')),
|
||||
DebugPage.route.go(context, args: DebugPageArgs(title: '${context.l10n.logs}(${BuildData.build})')),
|
||||
),
|
||||
Btn.icon(
|
||||
icon: const Icon(Icons.delete),
|
||||
|
||||
@@ -62,6 +62,7 @@ class _SnippetListPageState extends ConsumerState<SnippetListPage> with Automati
|
||||
tags: snippetState.tags.vn,
|
||||
onTagChanged: (tag) => _tag.value = tag,
|
||||
initTag: _tag.value,
|
||||
singleLine: true,
|
||||
),
|
||||
body: _buildSnippetList(snippets, tag),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
|
||||
@@ -250,6 +250,13 @@ class _AskAiSheetState extends ConsumerState<_AskAiSheet> {
|
||||
context.showSnackBar(libL10n.success);
|
||||
}
|
||||
|
||||
Future<void> _copyText(BuildContext context, String text) async {
|
||||
if (text.trim().isEmpty) return;
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
if (!mounted) return;
|
||||
context.showSnackBar(libL10n.success);
|
||||
}
|
||||
|
||||
void _sendMessage() {
|
||||
if (_isStreaming) return;
|
||||
final text = _inputController.text.trim();
|
||||
@@ -310,7 +317,23 @@ class _AskAiSheetState extends ConsumerState<_AskAiSheet> {
|
||||
streaming ? l10n.askAiAwaitingResponse : l10n.askAiNoResponse,
|
||||
style: theme.textTheme.bodySmall,
|
||||
)
|
||||
: SimpleMarkdown(data: content);
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SimpleMarkdown(data: content),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () => _copyText(context, content),
|
||||
icon: const Icon(Icons.copy, size: 18),
|
||||
label: Text(libL10n.copy),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: CardX(
|
||||
|
||||
@@ -252,7 +252,7 @@ class SSHPageState extends ConsumerState<SSHPage>
|
||||
deleteDetection: isMobile,
|
||||
autofocus: false,
|
||||
keyboardAppearance: _isDark ? Brightness.dark : Brightness.light,
|
||||
showToolbar: isMobile,
|
||||
showToolbar: true,
|
||||
viewOffset: Offset(2 * _horizonPadding, CustomAppBar.sysStatusBarHeight),
|
||||
hideScrollBar: false,
|
||||
focusNode: widget.args.focusNode,
|
||||
|
||||
@@ -123,7 +123,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
||||
|
||||
final item = items![index];
|
||||
final file = item.$1;
|
||||
final fileName = file.path.split('/').last;
|
||||
final fileName = file.path.split(Pfs.seperator).last;
|
||||
final stat = item.$2;
|
||||
final isDir = stat.type == FileSystemEntityType.directory;
|
||||
|
||||
@@ -140,11 +140,23 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
||||
required FileStat stat,
|
||||
required bool isDir,
|
||||
}) {
|
||||
final isServerFolder = isDir && file.parent.path == Paths.file;
|
||||
String? serverName;
|
||||
if (isServerFolder) {
|
||||
final servers = ref.read(serversProvider).servers;
|
||||
final server = servers[fileName];
|
||||
if (server != null) {
|
||||
serverName = server.name;
|
||||
}
|
||||
}
|
||||
|
||||
return CardX(
|
||||
child: ListTile(
|
||||
leading: isDir ? const Icon(Icons.folder_open) : const Icon(Icons.insert_drive_file),
|
||||
title: Text(fileName),
|
||||
subtitle: isDir ? null : Text(stat.size.bytes2Str, style: UIs.textGrey),
|
||||
title: Text(serverName ?? fileName),
|
||||
subtitle: isDir
|
||||
? (serverName != null ? Text(fileName, style: UIs.textGrey) : null)
|
||||
: Text(stat.size.bytes2Str, style: UIs.textGrey),
|
||||
trailing: Text(stat.modified.ymdhms(), style: UIs.textGrey),
|
||||
onLongPress: () {
|
||||
if (isDir) {
|
||||
@@ -216,7 +228,7 @@ extension _Actions on _LocalFilePageState {
|
||||
}
|
||||
|
||||
Future<void> _showFileActionDialog(FileSystemEntity file) async {
|
||||
final fileName = file.path.split('/').lastOrNull ?? '';
|
||||
final fileName = file.path.split(Pfs.seperator).lastOrNull ?? '';
|
||||
if (isPickFile) {
|
||||
context.showRoundDialog(
|
||||
title: libL10n.file,
|
||||
@@ -308,7 +320,7 @@ extension _Actions on _LocalFilePageState {
|
||||
}
|
||||
|
||||
void _showDeleteDialog(FileSystemEntity file) {
|
||||
final fileName = file.path.split('/').last;
|
||||
final fileName = file.path.split(Pfs.seperator).last;
|
||||
context.showRoundDialog(
|
||||
title: libL10n.delete,
|
||||
child: Text(libL10n.askContinue('${libL10n.delete} $fileName')),
|
||||
|
||||
@@ -177,32 +177,56 @@ extension _UI on _SftpPageState {
|
||||
|
||||
Widget _buildItem(SftpName file, {VoidCallback? beforeTap}) {
|
||||
final isDir = file.attr.isDirectory;
|
||||
final trailing = Text(
|
||||
'${_getTime(file.attr.modifyTime)}\n${file.attr.mode?.str ?? ''}',
|
||||
style: UIs.textGrey,
|
||||
textAlign: TextAlign.right,
|
||||
);
|
||||
return CardX(
|
||||
child: ListTile(
|
||||
leading: Icon(isDir ? Icons.folder_outlined : Icons.insert_drive_file),
|
||||
title: Text(file.filename),
|
||||
trailing: trailing,
|
||||
subtitle: isDir ? null : Text((file.attr.size ?? 0).bytes2Str, style: UIs.textGrey),
|
||||
onTap: () {
|
||||
beforeTap?.call();
|
||||
if (isDir) {
|
||||
_status.path.path = file.filename;
|
||||
_listDir();
|
||||
} else {
|
||||
_onItemPress(file, true);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
beforeTap?.call();
|
||||
_onItemPress(file, !isDir);
|
||||
},
|
||||
),
|
||||
);
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
if (screenWidth < 350) {
|
||||
return CardX(
|
||||
child: ListTile(
|
||||
leading: Icon(isDir ? Icons.folder_outlined : Icons.insert_drive_file),
|
||||
title: Text(file.filename),
|
||||
subtitle: isDir ? Text('${_getTime(file.attr.modifyTime)}\n${file.attr.mode?.str ?? ''}', style: UIs.textGrey) :
|
||||
Text('${(file.attr.size ?? 0).bytes2Str}\n${_getTime(file.attr.modifyTime)}\n${file.attr.mode?.str ?? ''}', style: UIs.textGrey),
|
||||
onTap: () {
|
||||
beforeTap?.call();
|
||||
if (isDir) {
|
||||
_status.path.path = file.filename;
|
||||
_listDir();
|
||||
} else {
|
||||
_onItemPress(file, true);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
beforeTap?.call();
|
||||
_onItemPress(file, !isDir);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CardX(
|
||||
child: ListTile(
|
||||
leading: Icon(isDir ? Icons.folder_outlined : Icons.insert_drive_file),
|
||||
title: Text(file.filename),
|
||||
trailing: Text(
|
||||
'${_getTime(file.attr.modifyTime)}\n${file.attr.mode?.str ?? ''}',
|
||||
style: UIs.textGrey,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
subtitle: isDir ? null : Text((file.attr.size ?? 0).bytes2Str, style: UIs.textGrey),
|
||||
onTap: () {
|
||||
beforeTap?.call();
|
||||
if (isDir) {
|
||||
_status.path.path = file.filename;
|
||||
_listDir();
|
||||
} else {
|
||||
_onItemPress(file, true);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
beforeTap?.call();
|
||||
_onItemPress(file, !isDir);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +585,11 @@ extension _Actions on _SftpPageState {
|
||||
|
||||
/// Local file dir + server id + remote path
|
||||
String _getLocalPath(String remotePath) {
|
||||
return Paths.file.joinPath(widget.args.spi.oldId).joinPath(remotePath);
|
||||
var normalizedPath = remotePath.replaceAll('/', Pfs.seperator);
|
||||
if (normalizedPath.startsWith(Pfs.seperator)) {
|
||||
normalizedPath = normalizedPath.substring(1);
|
||||
}
|
||||
return Paths.file.joinPath(widget.args.spi.id).joinPath(normalizedPath);
|
||||
}
|
||||
|
||||
/// Only return true if the path is changed
|
||||
|
||||
@@ -88,14 +88,14 @@ SPEC CHECKSUMS:
|
||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
icloud_storage: eb5b0f20687cf5a4fabc0b541f3b079cd6df7dcb
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: 8cdf29216ea1ab6b9743188287968d22b4579c1d
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
|
||||
@@ -481,7 +481,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -608,7 +608,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
|
||||
@@ -618,7 +618,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -638,7 +638,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1270;
|
||||
CURRENT_PROJECT_VERSION = 1291;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -649,7 +649,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1270;
|
||||
MARKETING_VERSION = 1.0.1291;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@@ -10,4 +10,19 @@ class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
if let controller = mainFlutterWindow?.contentViewController as? FlutterViewController {
|
||||
let channel = FlutterMethodChannel(name: "about", binaryMessenger: controller.engine.binaryMessenger)
|
||||
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
|
||||
if call.method == "showAboutPanel" {
|
||||
NSApp.orderFrontStandardAboutPanel(nil)
|
||||
result(nil)
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.applicationDidFinishLaunching(notification)
|
||||
}
|
||||
}
|
||||
|
||||
284
pubspec.lock
284
pubspec.lock
@@ -5,42 +5,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "85.0.0"
|
||||
version: "88.0.0"
|
||||
analyzer:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c
|
||||
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.6.0"
|
||||
version: "8.1.1"
|
||||
analyzer_buffer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_buffer
|
||||
sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43
|
||||
sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
version: "0.1.11"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin
|
||||
sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce
|
||||
sha256: dd574a0ab77de88b7d9c12bc4b626109a5ca9078216a79041a5c24c3a1bd103c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.4"
|
||||
version: "0.13.7"
|
||||
animations:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: animations
|
||||
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
|
||||
sha256: a8031b276f0a7986ac907195f10ca7cd04ecf2a8a566bd6dbe03018a9b02b427
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
version: "2.1.0"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -125,50 +125,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4"
|
||||
sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "4.0.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.2.0"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "4.1.1"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d
|
||||
sha256: "04f69b1502f66e22ae7990bbd01eb552b7f12793c4d3ea6e715d0ac5e98bcdac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.0"
|
||||
version: "2.10.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -181,42 +165,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.11.1"
|
||||
version: "8.12.0"
|
||||
camera:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera
|
||||
sha256: d6ec2cbdbe2fa8f5e0d07d8c06368fe4effa985a4a5ddade9cc58a8cd849557d
|
||||
sha256: eefad89f262a873f38d21e5eec853461737ea074d7c9ede39f3ceb135d201cab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.2"
|
||||
version: "0.11.3"
|
||||
camera_android_camerax:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android_camerax
|
||||
sha256: "2d438248554f44766bf9ea34c117a5bb0074e241342ef7c22c768fb431335234"
|
||||
sha256: d5256612833f9169c1698599a87370490622a188c5a7fb601169bb7b2f41f22b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.21"
|
||||
version: "0.6.24+1"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_avfoundation
|
||||
sha256: "951ef122d01ebba68b7a54bfe294e8b25585635a90465c311b2f875ae72c412f"
|
||||
sha256: "34bcd5db30e52414f1f0783c5e3f566909fab14141a21b3b576c78bd35382bf6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.21+2"
|
||||
version: "0.9.22+4"
|
||||
camera_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_platform_interface
|
||||
sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2"
|
||||
sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
version: "2.12.0"
|
||||
camera_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -286,10 +270,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
version: "4.11.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -327,18 +311,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
version: "0.3.5"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -351,26 +335,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: cc4684d22ca05bf0a4a51127e19a8aea576b42079ed2bc9e956f11aaebe35dd1
|
||||
sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.8.1"
|
||||
custom_lint_visitor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_visitor
|
||||
sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2"
|
||||
sha256: "446d68322747ec1c36797090de776aa72228818d3d80685a91ff524d163fee6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+7.7.0"
|
||||
version: "1.0.0+8.1.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
||||
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.1.2"
|
||||
dartssh2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -472,10 +456,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||
sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.2"
|
||||
version: "10.3.6"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -488,8 +472,8 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v1.0.52"
|
||||
resolved-ref: "38e7d41ccd71bf44e286d86b4ad656f05c5c2548"
|
||||
ref: "v1.0.53"
|
||||
resolved-ref: "61ee37ea6f082592f5be56340b7746dce4ffbfda"
|
||||
url: "https://github.com/lppcg/fl_build.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
@@ -497,10 +481,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: d3f82f4a38e33ba23d05a08ff304d7d8b22d2a59a5503f20bd802966e915db89
|
||||
sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
fl_lib:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -580,26 +564,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_native_splash
|
||||
sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc"
|
||||
sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.6"
|
||||
version: "2.4.7"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||
sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.30"
|
||||
version: "2.0.32"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "71a420767ae786f9402e4efb74e1119fa95b53e6a3781ab8ab21c121ac1349c6"
|
||||
sha256: "9e2d6907f12cc7d23a846847615941bddee8709bf2bfd274acdf5e80bcf22fde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.3"
|
||||
flutter_secure_storage:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -660,10 +644,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
|
||||
sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -702,10 +686,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: get_it
|
||||
sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
|
||||
sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.0"
|
||||
version: "9.0.5"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -743,26 +727,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive_ce
|
||||
sha256: "708bb39050998707c5d422752159f91944d3c81ab42d80e1bd0ee37d8e130658"
|
||||
sha256: "81d39a03c4c0ba5938260a8c3547d2e71af59defecea21793d57fc3551f0d230"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.3"
|
||||
version: "2.15.1"
|
||||
hive_ce_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive_ce_flutter
|
||||
sha256: f5bd57fda84402bca7557fedb8c629c96c8ea10fab4a542968d7b60864ca02cc
|
||||
sha256: "26d656c9e8974f0732f1d09020e2d7b08ba841b8961a02dbfb6caf01474b0e9a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.3"
|
||||
hive_ce_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_ce_generator
|
||||
sha256: a169feeff2da9cc2c417ce5ae9bcebf7c8a95d7a700492b276909016ad70a786
|
||||
sha256: b19ac263cb37529513508ba47352c41e6de72ba879952898d9c18c9c8a955921
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.3"
|
||||
version: "1.10.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -775,10 +759,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
http_client_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -895,10 +879,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -935,26 +919,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
|
||||
sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.52"
|
||||
version: "1.0.56"
|
||||
local_auth_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_darwin
|
||||
sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
|
||||
sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
version: "1.6.1"
|
||||
local_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_platform_interface
|
||||
sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a"
|
||||
sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
version: "1.1.0"
|
||||
local_auth_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1007,10 +991,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1023,10 +1007,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mockito
|
||||
sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99"
|
||||
sha256: "4feb43bc4eb6c03e832f5fcd637d1abb44b98f9cfa245c58e27382f58859f8f6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
version: "5.5.1"
|
||||
multi_split_view:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1063,10 +1047,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
||||
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.1"
|
||||
version: "9.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1103,18 +1087,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
|
||||
sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.18"
|
||||
version: "2.2.20"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||
sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1192,10 +1176,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
version: "1.5.2"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1256,10 +1240,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr_code_dart_scan
|
||||
sha256: "1b317b47f475f6995c19e0f41d790902a8cd158b23c435d936763d86ba44309c"
|
||||
sha256: "81443d940f8f27baaa4b9aeaa8d3d2155ad2c0b9842a9bacb03dab85c111e2f6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.3"
|
||||
version: "0.11.5"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1296,10 +1280,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: b21446f04474040479b19004c5b729605cde221bbd14cf16ad0db8804dce4810
|
||||
sha256: c406de02bff19d920b832bddfb8283548bfa05ce41c59afba57ce643e116aa59
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.3"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1312,18 +1296,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: d8154e04008b98015ee3e0514a5d929e3d15605790d28bcd74e63e2d415632a1
|
||||
sha256: "7230014155777fc31ba3351bc2cb5a3b5717b11bfafe52b1553cb47d385f8897"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.3"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: af92fa4051568071f7e12bd44b8b4f3adc7470e3676bd5ac953582a9cce4a1c0
|
||||
sha256: "49894543a42cf7a9954fc4e7366b6d3cb2e6ec0fa07775f660afcdd92d097702"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.3"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1376,10 +1360,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6"
|
||||
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.0"
|
||||
version: "12.0.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1400,18 +1384,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
||||
sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.12"
|
||||
version: "2.4.15"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
version: "2.5.5"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1485,18 +1469,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
|
||||
sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.0.2"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
|
||||
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.7"
|
||||
version: "1.3.8"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1521,14 +1505,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1581,34 +1557,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
|
||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.26.2"
|
||||
version: "1.26.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
version: "0.7.7"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
|
||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.11"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "0.6.12"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1634,7 +1602,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
@@ -1645,18 +1613,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
|
||||
sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.18"
|
||||
version: "6.3.24"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
version: "6.3.5"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1669,10 +1637,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
version: "3.2.4"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1701,10 +1669,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
version: "4.5.2"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1757,18 +1725,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678
|
||||
sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.4.0"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207
|
||||
sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
version: "1.3.0"
|
||||
watch_connectivity:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1782,10 +1750,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
version: "1.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1830,10 +1798,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.0"
|
||||
version: "5.15.0"
|
||||
window_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1901,4 +1869,4 @@ packages:
|
||||
version: "1.1.4"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
flutter: ">=3.38.0"
|
||||
|
||||
11
pubspec.yaml
11
pubspec.yaml
@@ -1,11 +1,11 @@
|
||||
name: server_box
|
||||
description: server status & toolbox app.
|
||||
publish_to: "none"
|
||||
version: 1.0.1270+1270
|
||||
version: 1.0.1291+1291
|
||||
|
||||
environment:
|
||||
sdk: ">=3.9.0"
|
||||
flutter: ">=3.35.0"
|
||||
flutter: ">=3.38.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -25,7 +25,7 @@ dependencies:
|
||||
flutter_displaymode: ^0.7.0
|
||||
fl_chart: ^1.0.0
|
||||
freezed_annotation: ^3.0.0
|
||||
get_it: ^8.2.0
|
||||
get_it: ^9.0.5
|
||||
highlight: ^0.7.0
|
||||
hive_ce_flutter: ^2.3.1
|
||||
intl: ^0.20.2
|
||||
@@ -38,6 +38,7 @@ dependencies:
|
||||
wake_on_lan: ^4.1.1+3
|
||||
webdav_client_plus: ^1.0.2
|
||||
xml: ^6.4.2 # for parsing nvidia-smi
|
||||
url_launcher: ^6.2.6
|
||||
dartssh2:
|
||||
git:
|
||||
url: https://github.com/lollipopkit/dartssh2
|
||||
@@ -84,7 +85,7 @@ dependency_overrides:
|
||||
ref: v0.0.36
|
||||
|
||||
dev_dependencies:
|
||||
analyzer: ^7.3.0
|
||||
analyzer: ^8.1.1
|
||||
flutter_native_splash: ^2.1.6
|
||||
hive_ce_generator: ^1.9.2
|
||||
build_runner: ^2.4.15
|
||||
@@ -102,7 +103,7 @@ dev_dependencies:
|
||||
fl_build:
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_build.git
|
||||
ref: v1.0.52
|
||||
ref: v1.0.53
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
|
||||
Reference in New Issue
Block a user