Compare commits

..

15 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
874d28be12 bump: v1291 2026-01-14 13:32:30 +08:00
GT610
06070c29b9 fix(color-picking): Fix color picking failure and card overflow (#998) 2026-01-11 00:21:48 +08:00
GT610
bb0ada12e6 bump: Update Android build tools and Actions version (#997) 2026-01-10 20:03:37 +08:00
GT610
9ceeaf7cc4 feat(local file page): Display server names for server folders (#996)
* feat(local file page): Display server names for server folders

In the local file list, server folders will display their corresponding server names, enhancing the user experience.

* fix(storage page): Use ref.read instead of ref.watch to fetch the server list

Avoid unnecessary watch operations during construction, reducing potential performance overhead
2026-01-08 18:57:02 +08:00
GT610
29a57ad742 fix(container): Modify container execution commands to prioritize bash or ash (#995) 2026-01-08 09:11:23 +08:00
GT610
2c495a44c3 fix(log): Logging System Improvements and Error Handling Enhancements (#994)
* fix: Added logging to exception handling

Added detailed error logging to exception handling across multiple files, including exception information and stack traces, to facilitate troubleshooting.

* refactor(logging): Standardize logging output methods

Replace existing debugPrint and lprint with Loggers.app.warning to enhance logging consistency and maintainability.

* refactor: Remove redundant debug log prints

Clean up unnecessary log print statements in debug code

* feat(i18n): Added internationalization support for the logging feature
2026-01-07 15:09:22 +08:00
GT610
cc300c141a refactor(sftp): Replace hard-coded path separators with Pfs.seperator (#993)
Unify the use of Pfs.seperator for handling file path separators to enhance cross-platform compatibility.
2026-01-06 23:50:11 +08:00
GT610
26efb8e185 fix: Add input validation and bounds checking to parsing methods (#990)
* fix: Resolved boundary condition issues in string processing

Addressed null and length checks during string splitting across multiple model classes to prevent potential null pointer exceptions and array out-of-bounds errors

* fix: Throw exceptions instead of silently returning when package manager output formats are invalid

Modified the _pacman, _opkg, and _apk parsing methods to throw exceptions when input formats are invalid, rather than silently returning, to prevent potential error handling issues.
2026-01-06 23:47:49 +08:00
GT610
06ed38ff45 fix(container): Fix Podman 5.x Network Traffic Statistics Not Displaying (#991)
* fix(container): Added version parameter to accommodate Podman 5.x network statistics format

Modified the parseStats method to accept a version parameter, handling changes in Podman 5.x's network statistics data structure. When the version is 5.x, network traffic data is retrieved from the RxBytes/TxBytes fields of the Network interface.

* fix(container): Fixed Podman version detection logic to correctly retrieve network statistics

Addressed Podman version number parsing issues and improved version comparison logic to support all 4.x and below versions as well as 5.x and above versions

* fix(container): Resolved display formatting issues for network and disk I/O statistics

Handled default values when NetIO and BlockIO are null, and reformatted display strings to distinguish upstream/downstream traffic and read/write operations.

* fix: Why did I mess up the tag order?
2026-01-06 23:44:54 +08:00
GT610
7c35abe30e fix(cpu): Resolved boundary condition issues when calculating CPU utilization (#988)
Added checks for coreIdx out-of-bounds and totalDelta being zero to prevent array out-of-bounds and division-by-zero errors
2026-01-06 12:48:13 +08:00
lxdklp
78ef181d4a feat: support macOS menubar (#976)
* feat: macOS menubar

* feat: Dynamic NavigateMenuItems

* fix: simplify shortcut config

* fix: Simplify the code

* fix: More suitable tab name
2025-12-10 18:05:30 +08:00
lollipopkit🏳️‍⚧️
3f15caeaf2 new: add copy btn for ask ai (#975) 2025-12-07 17:51:07 +08:00
lollipopkit🏳️‍⚧️
6458e736fa fix: tag switcher ui (#974)
Fixes #964
2025-12-07 17:29:12 +08:00
lollipopkit🏳️‍⚧️
99fda8b747 opt. 2025-12-07 17:19:24 +08:00
lxdklp
c5cbb12ac3 feat: Automatic line wrapping of time (#973) 2025-12-07 17:14:45 +08:00
67 changed files with 823 additions and 151 deletions

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 1

View File

@@ -17,10 +17,10 @@ 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:
@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -748,7 +748,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
MARKETING_VERSION = 1.0.1291;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox;

View File

@@ -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);

View File

@@ -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.

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View 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);
}
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -33,9 +33,11 @@ class Cpus extends TimeSeq<SingleCpuCore> {
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) {
@@ -164,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,

View File

@@ -164,7 +164,8 @@ class NetSpeed extends TimeSeq<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;
}
}

View File

@@ -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] : '';
}
}

View File

@@ -679,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);
}
}

View File

@@ -21,7 +21,8 @@ class SftpReq {
}
try {
knownHostFingerprints = Map<String, String>.from(Stores.setting.sshKnownHostFingerprints.get());
} catch (_) {
} catch (e, s) {
Loggers.app.warning('Failed to load SSH known host fingerprints', e, s);
knownHostFingerprints = null;
}
}
@@ -36,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;

View File

@@ -70,7 +70,7 @@ Future<void> _download(SftpReq req, SendPort mainSendPort, SendErrorFunction sen
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

View File

@@ -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 \\"'

View File

@@ -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);
}
}
}
}

View File

@@ -292,7 +292,7 @@ class ServerNotifier extends _$ServerNotifier {
try {
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');
// Loggers.app.info('Running status command for ${spi.name} (${state.status.system.name}): $statusCmd');
final execResult = await state.client?.run(statusCmd);
if (execResult != null) {
raw = SSHDecoder.decode(
@@ -300,7 +300,7 @@ class ServerNotifier extends _$ServerNotifier {
isWindows: state.status.system == SystemType.windows,
context: 'GetStatus<${spi.name}>',
);
Loggers.app.info('Status response length for ${spi.name}: ${raw.length} bytes');
// Loggers.app.info('Status response length for ${spi.name}: ${raw.length} bytes');
} else {
raw = '';
Loggers.app.warning('No status result from ${spi.name}');

View File

@@ -3,6 +3,6 @@
abstract class BuildData {
static const String name = "ServerBox";
static const int build = 1276;
static const int build = 1291;
static const int script = 70;
}

View File

@@ -22,6 +22,7 @@ abstract final class GithubIds {
'MasedMSD',
'GitGitro',
'Shin-suechtig',
'GT-610'
};
static const participants = <GhId>{

View File

@@ -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

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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 => 'ログ';
}

View File

@@ -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';
}

View File

@@ -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';
}

View File

@@ -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 => 'Журналы';
}

View File

@@ -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';
}

View File

@@ -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 => 'Журнали';
}

View File

@@ -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 => '日誌';
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -293,5 +293,6 @@
"wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
"write": "書き込み",
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。"
"writeScriptTip": "サーバーへの接続後、システムステータスを監視するスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。",
"logs": "ログ"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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": "Журналы"
}

View File

@@ -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"
}

View File

@@ -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": "Журнали"
}

View File

@@ -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": "日志"
}

View File

@@ -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": "日誌"
}

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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),

View File

@@ -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(

View File

@@ -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(

View File

@@ -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,

View File

@@ -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')),

View File

@@ -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

View File

@@ -471,7 +471,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1276;
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.1276;
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 = 1276;
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.1276;
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 = 1276;
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.1276;
MARKETING_VERSION = 1.0.1291;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -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)
}
}

View File

@@ -472,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"
@@ -1602,7 +1602,7 @@ packages:
source: hosted
version: "2.2.2"
url_launcher:
dependency: transitive
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8

View File

@@ -1,7 +1,7 @@
name: server_box
description: server status & toolbox app.
publish_to: "none"
version: 1.0.1276+1276
version: 1.0.1291+1291
environment:
sdk: ">=3.9.0"
@@ -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
@@ -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