mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
new: podman
This commit is contained in:
@@ -328,6 +328,12 @@ abstract class S {
|
|||||||
/// **'Connected'**
|
/// **'Connected'**
|
||||||
String get connected;
|
String get connected;
|
||||||
|
|
||||||
|
/// No description provided for @container.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Container'**
|
||||||
|
String get container;
|
||||||
|
|
||||||
/// No description provided for @containerName.
|
/// No description provided for @containerName.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1420,6 +1426,12 @@ abstract class S {
|
|||||||
/// **'The suspend function requires root privileges and systemd support.'**
|
/// **'The suspend function requires root privileges and systemd support.'**
|
||||||
String get suspendTip;
|
String get suspendTip;
|
||||||
|
|
||||||
|
/// No description provided for @switchTo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Switch to {val}'**
|
||||||
|
String switchTo(Object val);
|
||||||
|
|
||||||
/// No description provided for @syncTip.
|
/// No description provided for @syncTip.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get connected => 'in Verbindung gebracht';
|
String get connected => 'in Verbindung gebracht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => 'Container';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => 'Container Name';
|
String get containerName => 'Container Name';
|
||||||
|
|
||||||
@@ -694,6 +697,11 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.';
|
String get suspendTip => 'Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return 'Wechseln zu $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
|
String get syncTip => 'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get connected => 'Connected';
|
String get connected => 'Connected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => 'Container';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => 'Container name';
|
String get containerName => 'Container name';
|
||||||
|
|
||||||
@@ -694,6 +697,11 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'The suspend function requires root privileges and systemd support.';
|
String get suspendTip => 'The suspend function requires root privileges and systemd support.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return 'Switch to $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'A restart may be required for some changes to take effect.';
|
String get syncTip => 'A restart may be required for some changes to take effect.';
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class SFr extends S {
|
|||||||
@override
|
@override
|
||||||
String get connected => 'Connecté';
|
String get connected => 'Connecté';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => 'Conteneurs';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => 'Nom du conteneur';
|
String get containerName => 'Nom du conteneur';
|
||||||
|
|
||||||
@@ -694,6 +697,11 @@ class SFr extends S {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'La fonction de suspension nécessite des privilèges root et la prise en charge de systemd.';
|
String get suspendTip => 'La fonction de suspension nécessite des privilèges root et la prise en charge de systemd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return 'Passer à $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'Un redémarrage peut être nécessaire pour que certains changements prennent effet.';
|
String get syncTip => 'Un redémarrage peut être nécessaire pour que certains changements prennent effet.';
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class SId extends S {
|
|||||||
@override
|
@override
|
||||||
String get connected => 'Terhubung';
|
String get connected => 'Terhubung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => 'Wadah';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => 'Nama kontainer';
|
String get containerName => 'Nama kontainer';
|
||||||
|
|
||||||
@@ -694,6 +697,11 @@ class SId extends S {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.';
|
String get suspendTip => 'Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return 'Beralih ke $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => 'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.';
|
String get syncTip => 'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.';
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get connected => '已连接';
|
String get connected => '已连接';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => '容器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => '容器名';
|
String get containerName => '容器名';
|
||||||
|
|
||||||
@@ -694,6 +697,11 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'suspend 功能需要 root 权限及 systemd 支持。';
|
String get suspendTip => 'suspend 功能需要 root 权限及 systemd 支持。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return '切换到 $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '可能需要重新启动,某些更改才能生效。';
|
String get syncTip => '可能需要重新启动,某些更改才能生效。';
|
||||||
|
|
||||||
@@ -948,6 +956,9 @@ class SZhTw extends SZh {
|
|||||||
@override
|
@override
|
||||||
String get connected => '已連接';
|
String get connected => '已連接';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get container => '容器';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get containerName => '容器名稱';
|
String get containerName => '容器名稱';
|
||||||
|
|
||||||
@@ -1522,6 +1533,11 @@ class SZhTw extends SZh {
|
|||||||
@override
|
@override
|
||||||
String get suspendTip => 'suspend 功能需要 root 權限及 systemd 支持。';
|
String get suspendTip => 'suspend 功能需要 root 權限及 systemd 支持。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String switchTo(Object val) {
|
||||||
|
return '切換到 $val';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get syncTip => '可能需要重新啟動,某些更改才能生效。';
|
String get syncTip => '可能需要重新啟動,某些更改才能生效。';
|
||||||
|
|
||||||
|
|||||||
@@ -586,7 +586,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -730,7 +730,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -748,7 +748,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||||
@@ -758,7 +758,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
@@ -779,7 +779,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -792,7 +792,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
@@ -818,7 +818,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -831,7 +831,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -854,7 +854,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -867,7 +867,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -890,7 +890,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -902,7 +902,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||||
@@ -931,7 +931,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -943,7 +943,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||||
PRODUCT_NAME = ServerBox;
|
PRODUCT_NAME = ServerBox;
|
||||||
@@ -969,7 +969,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 707;
|
CURRENT_PROJECT_VERSION = 709;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BA88US33G6;
|
DEVELOPMENT_TEAM = BA88US33G6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -981,7 +981,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.707;
|
MARKETING_VERSION = 1.0.709;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||||
PRODUCT_NAME = ServerBox;
|
PRODUCT_NAME = ServerBox;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:toolbox/core/analysis.dart';
|
|||||||
import 'package:toolbox/data/model/server/private_key_info.dart';
|
import 'package:toolbox/data/model/server/private_key_info.dart';
|
||||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||||
import 'package:toolbox/view/page/backup.dart';
|
import 'package:toolbox/view/page/backup.dart';
|
||||||
import 'package:toolbox/view/page/docker.dart';
|
import 'package:toolbox/view/page/container.dart';
|
||||||
import 'package:toolbox/view/page/home.dart';
|
import 'package:toolbox/view/page/home.dart';
|
||||||
import 'package:toolbox/view/page/ping.dart';
|
import 'package:toolbox/view/page/ping.dart';
|
||||||
import 'package:toolbox/view/page/private_key/edit.dart';
|
import 'package:toolbox/view/page/private_key/edit.dart';
|
||||||
@@ -150,7 +150,7 @@ class AppRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AppRoute docker({Key? key, required ServerPrivateInfo spi}) {
|
static AppRoute docker({Key? key, required ServerPrivateInfo spi}) {
|
||||||
return AppRoute(DockerManagePage(key: key, spi: spi), 'docker');
|
return AppRoute(ContainerPage(key: key, spi: spi), 'docker');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - Pop true if the text is changed & [path] is not null
|
/// - Pop true if the text is changed & [path] is not null
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Backup {
|
|||||||
final List<ServerPrivateInfo> spis;
|
final List<ServerPrivateInfo> spis;
|
||||||
final List<Snippet> snippets;
|
final List<Snippet> snippets;
|
||||||
final List<PrivateKeyInfo> keys;
|
final List<PrivateKeyInfo> keys;
|
||||||
final Map<String, dynamic> dockerHosts;
|
final Map<String, dynamic> container;
|
||||||
final Map<String, dynamic> settings;
|
final Map<String, dynamic> settings;
|
||||||
final Map<String, dynamic> history;
|
final Map<String, dynamic> history;
|
||||||
final int? lastModTime;
|
final int? lastModTime;
|
||||||
@@ -31,7 +31,7 @@ class Backup {
|
|||||||
required this.spis,
|
required this.spis,
|
||||||
required this.snippets,
|
required this.snippets,
|
||||||
required this.keys,
|
required this.keys,
|
||||||
required this.dockerHosts,
|
required this.container,
|
||||||
required this.settings,
|
required this.settings,
|
||||||
required this.history,
|
required this.history,
|
||||||
this.lastModTime,
|
this.lastModTime,
|
||||||
@@ -48,7 +48,7 @@ class Backup {
|
|||||||
keys = (json['keys'] as List)
|
keys = (json['keys'] as List)
|
||||||
.map((e) => PrivateKeyInfo.fromJson(e))
|
.map((e) => PrivateKeyInfo.fromJson(e))
|
||||||
.toList(),
|
.toList(),
|
||||||
dockerHosts = json['dockerHosts'] ?? {},
|
container = json['container'] ?? {},
|
||||||
settings = json['settings'] ?? {},
|
settings = json['settings'] ?? {},
|
||||||
lastModTime = json['lastModTime'],
|
lastModTime = json['lastModTime'],
|
||||||
history = json['history'] ?? {};
|
history = json['history'] ?? {};
|
||||||
@@ -59,7 +59,7 @@ class Backup {
|
|||||||
'spis': spis,
|
'spis': spis,
|
||||||
'snippets': snippets,
|
'snippets': snippets,
|
||||||
'keys': keys,
|
'keys': keys,
|
||||||
'dockerHosts': dockerHosts,
|
'container': container,
|
||||||
'settings': settings,
|
'settings': settings,
|
||||||
'lastModTime': lastModTime,
|
'lastModTime': lastModTime,
|
||||||
'history': history,
|
'history': history,
|
||||||
@@ -71,7 +71,7 @@ class Backup {
|
|||||||
spis = Stores.server.fetch(),
|
spis = Stores.server.fetch(),
|
||||||
snippets = Stores.snippet.fetch(),
|
snippets = Stores.snippet.fetch(),
|
||||||
keys = Stores.key.fetch(),
|
keys = Stores.key.fetch(),
|
||||||
dockerHosts = Stores.docker.box.toJson(),
|
container = Stores.docker.box.toJson(),
|
||||||
settings = Stores.setting.box.toJson(),
|
settings = Stores.setting.box.toJson(),
|
||||||
lastModTime = Stores.lastModTime,
|
lastModTime = Stores.lastModTime,
|
||||||
history = Stores.history.box.toJson();
|
history = Stores.history.box.toJson();
|
||||||
@@ -110,8 +110,8 @@ class Backup {
|
|||||||
for (final s in history.keys) {
|
for (final s in history.keys) {
|
||||||
Stores.history.box.put(s, history[s]);
|
Stores.history.box.put(s, history[s]);
|
||||||
}
|
}
|
||||||
for (final k in dockerHosts.keys) {
|
for (final k in container.keys) {
|
||||||
final val = dockerHosts[k];
|
final val = container[k];
|
||||||
if (val != null && val is String && val.isNotEmpty) {
|
if (val != null && val is String && val.isNotEmpty) {
|
||||||
Stores.docker.put(k, val);
|
Stores.docker.put(k, val);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SSHErr extends Err<SSHErrType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DockerErrType {
|
enum ContainerErrType {
|
||||||
unknown,
|
unknown,
|
||||||
noClient,
|
noClient,
|
||||||
notInstalled,
|
notInstalled,
|
||||||
@@ -45,12 +45,12 @@ enum DockerErrType {
|
|||||||
parseStats,
|
parseStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DockerErr extends Err<DockerErrType> {
|
class ContainerErr extends Err<ContainerErrType> {
|
||||||
DockerErr({required super.type, super.message}) : super(from: ErrFrom.docker);
|
ContainerErr({required super.type, super.message}) : super(from: ErrFrom.docker);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'DockerErr<$type>: $message';
|
return 'ContainerErr<$type>: $message';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:toolbox/core/extension/context/locale.dart';
|
||||||
|
|
||||||
enum ServerTabMenuType {
|
enum ServerTabMenu {
|
||||||
terminal,
|
terminal,
|
||||||
sftp,
|
sftp,
|
||||||
docker,
|
container,
|
||||||
process,
|
process,
|
||||||
pkg,
|
pkg,
|
||||||
//snippet,
|
//snippet,
|
||||||
@@ -12,40 +12,40 @@ enum ServerTabMenuType {
|
|||||||
|
|
||||||
IconData get icon {
|
IconData get icon {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case ServerTabMenuType.sftp:
|
case ServerTabMenu.sftp:
|
||||||
return Icons.insert_drive_file;
|
return Icons.insert_drive_file;
|
||||||
//case ServerTabMenuType.snippet:
|
//case ServerTabMenuType.snippet:
|
||||||
//return Icons.code;
|
//return Icons.code;
|
||||||
case ServerTabMenuType.pkg:
|
case ServerTabMenu.pkg:
|
||||||
return Icons.system_security_update;
|
return Icons.system_security_update;
|
||||||
case ServerTabMenuType.docker:
|
case ServerTabMenu.container:
|
||||||
return Icons.view_agenda;
|
return Icons.view_agenda;
|
||||||
case ServerTabMenuType.process:
|
case ServerTabMenu.process:
|
||||||
return Icons.list_alt_outlined;
|
return Icons.list_alt_outlined;
|
||||||
case ServerTabMenuType.terminal:
|
case ServerTabMenu.terminal:
|
||||||
return Icons.terminal;
|
return Icons.terminal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get toStr {
|
String get toStr {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case ServerTabMenuType.sftp:
|
case ServerTabMenu.sftp:
|
||||||
return 'SFTP';
|
return 'SFTP';
|
||||||
//case ServerTabMenuType.snippet:
|
//case ServerTabMenuType.snippet:
|
||||||
//return l10n.snippet;
|
//return l10n.snippet;
|
||||||
case ServerTabMenuType.pkg:
|
case ServerTabMenu.pkg:
|
||||||
return l10n.pkg;
|
return l10n.pkg;
|
||||||
case ServerTabMenuType.docker:
|
case ServerTabMenu.container:
|
||||||
return 'Docker';
|
return l10n.container;
|
||||||
case ServerTabMenuType.process:
|
case ServerTabMenu.process:
|
||||||
return l10n.process;
|
return l10n.process;
|
||||||
case ServerTabMenuType.terminal:
|
case ServerTabMenu.terminal:
|
||||||
return l10n.terminal;
|
return l10n.terminal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DockerMenuType {
|
enum ContainerMenu {
|
||||||
start,
|
start,
|
||||||
stop,
|
stop,
|
||||||
restart,
|
restart,
|
||||||
@@ -55,7 +55,7 @@ enum DockerMenuType {
|
|||||||
//stats,
|
//stats,
|
||||||
;
|
;
|
||||||
|
|
||||||
static List<DockerMenuType> items(bool running) {
|
static List<ContainerMenu> items(bool running) {
|
||||||
if (running) {
|
if (running) {
|
||||||
return [
|
return [
|
||||||
stop,
|
stop,
|
||||||
@@ -72,17 +72,17 @@ enum DockerMenuType {
|
|||||||
|
|
||||||
IconData get icon {
|
IconData get icon {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case DockerMenuType.start:
|
case ContainerMenu.start:
|
||||||
return Icons.play_arrow;
|
return Icons.play_arrow;
|
||||||
case DockerMenuType.stop:
|
case ContainerMenu.stop:
|
||||||
return Icons.stop;
|
return Icons.stop;
|
||||||
case DockerMenuType.restart:
|
case ContainerMenu.restart:
|
||||||
return Icons.restart_alt;
|
return Icons.restart_alt;
|
||||||
case DockerMenuType.rm:
|
case ContainerMenu.rm:
|
||||||
return Icons.delete;
|
return Icons.delete;
|
||||||
case DockerMenuType.logs:
|
case ContainerMenu.logs:
|
||||||
return Icons.logo_dev;
|
return Icons.logo_dev;
|
||||||
case DockerMenuType.terminal:
|
case ContainerMenu.terminal:
|
||||||
return Icons.terminal;
|
return Icons.terminal;
|
||||||
// case DockerMenuType.stats:
|
// case DockerMenuType.stats:
|
||||||
// return Icons.bar_chart;
|
// return Icons.bar_chart;
|
||||||
@@ -91,24 +91,24 @@ enum DockerMenuType {
|
|||||||
|
|
||||||
String get toStr {
|
String get toStr {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case DockerMenuType.start:
|
case ContainerMenu.start:
|
||||||
return l10n.start;
|
return l10n.start;
|
||||||
case DockerMenuType.stop:
|
case ContainerMenu.stop:
|
||||||
return l10n.stop;
|
return l10n.stop;
|
||||||
case DockerMenuType.restart:
|
case ContainerMenu.restart:
|
||||||
return l10n.restart;
|
return l10n.restart;
|
||||||
case DockerMenuType.rm:
|
case ContainerMenu.rm:
|
||||||
return l10n.delete;
|
return l10n.delete;
|
||||||
case DockerMenuType.logs:
|
case ContainerMenu.logs:
|
||||||
return l10n.log;
|
return l10n.log;
|
||||||
case DockerMenuType.terminal:
|
case ContainerMenu.terminal:
|
||||||
return l10n.terminal;
|
return l10n.terminal;
|
||||||
// case DockerMenuType.stats:
|
// case DockerMenuType.stats:
|
||||||
// return s.stats;
|
// return s.stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupMenuItem<DockerMenuType> get widget => _build(this, icon, toStr);
|
PopupMenuItem<ContainerMenu> get widget => _build(this, icon, toStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupMenuItem<T> _build<T>(T t, IconData icon, String text) {
|
PopupMenuItem<T> _build<T>(T t, IconData icon, String text) {
|
||||||
|
|||||||
@@ -224,30 +224,6 @@ const _statusCmds = [
|
|||||||
'nvidia-smi -q -x',
|
'nvidia-smi -q -x',
|
||||||
];
|
];
|
||||||
|
|
||||||
enum DockerCmdType {
|
|
||||||
version,
|
|
||||||
ps,
|
|
||||||
//stats,
|
|
||||||
images,
|
|
||||||
;
|
|
||||||
|
|
||||||
String get exec {
|
|
||||||
switch (this) {
|
|
||||||
case DockerCmdType.version:
|
|
||||||
return 'docker version';
|
|
||||||
case DockerCmdType.ps:
|
|
||||||
return 'docker ps -a';
|
|
||||||
// case DockerCmdType.stats:
|
|
||||||
// return 'docker stats --no-stream';
|
|
||||||
case DockerCmdType.images:
|
|
||||||
return 'docker image ls';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final execAll =
|
|
||||||
values.map((e) => e.exec).join(' && echo $seperator && ');
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BSDStatusCmdType {
|
enum BSDStatusCmdType {
|
||||||
echo,
|
echo,
|
||||||
time,
|
time,
|
||||||
|
|||||||
115
lib/data/model/container/image.dart
Normal file
115
lib/data/model/container/image.dart
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:toolbox/core/extension/numx.dart';
|
||||||
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
|
|
||||||
|
abstract final class ContainerImg {
|
||||||
|
final String? repository = null;
|
||||||
|
final String? tag = null;
|
||||||
|
final String? id = null;
|
||||||
|
String? get sizeMB;
|
||||||
|
int? get containersCount;
|
||||||
|
|
||||||
|
factory ContainerImg.fromRawJson(String s, ContainerType typ) => typ.img(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PodmanImg implements ContainerImg {
|
||||||
|
@override
|
||||||
|
final String? repository;
|
||||||
|
@override
|
||||||
|
final String? tag;
|
||||||
|
@override
|
||||||
|
final String? id;
|
||||||
|
final int? created;
|
||||||
|
final int? size;
|
||||||
|
final int? containers;
|
||||||
|
|
||||||
|
PodmanImg({
|
||||||
|
this.repository,
|
||||||
|
this.tag,
|
||||||
|
this.id,
|
||||||
|
this.created,
|
||||||
|
this.size,
|
||||||
|
this.containers,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get sizeMB => size?.convertBytes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? get containersCount => containers;
|
||||||
|
|
||||||
|
factory PodmanImg.fromRawJson(String str) =>
|
||||||
|
PodmanImg.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
|
||||||
|
repository: json["repository"],
|
||||||
|
tag: json["tag"],
|
||||||
|
id: json["Id"],
|
||||||
|
created: json["Created"],
|
||||||
|
size: json["Size"],
|
||||||
|
containers: json["Containers"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"repository": repository,
|
||||||
|
"tag": tag,
|
||||||
|
"Id": id,
|
||||||
|
"Created": created,
|
||||||
|
"Size": size,
|
||||||
|
"Containers": containers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DockerImg implements ContainerImg {
|
||||||
|
final String containers;
|
||||||
|
final String createdAt;
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String repository;
|
||||||
|
final String size;
|
||||||
|
@override
|
||||||
|
final String tag;
|
||||||
|
|
||||||
|
DockerImg({
|
||||||
|
required this.containers,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.id,
|
||||||
|
required this.repository,
|
||||||
|
required this.size,
|
||||||
|
required this.tag,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get sizeMB => size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? get containersCount =>
|
||||||
|
containers == 'N/A' ? 0 : int.tryParse(containers);
|
||||||
|
|
||||||
|
factory DockerImg.fromRawJson(String str) =>
|
||||||
|
DockerImg.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory DockerImg.fromJson(Map<String, dynamic> json) => DockerImg(
|
||||||
|
containers: json["Containers"],
|
||||||
|
createdAt: json["CreatedAt"],
|
||||||
|
id: json["ID"],
|
||||||
|
repository: json["Repository"],
|
||||||
|
size: json["Size"],
|
||||||
|
tag: json["Tag"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"Containers": containers,
|
||||||
|
"CreatedAt": createdAt,
|
||||||
|
"ID": id,
|
||||||
|
"Repository": repository,
|
||||||
|
"Size": size,
|
||||||
|
"Tag": tag,
|
||||||
|
};
|
||||||
|
}
|
||||||
127
lib/data/model/container/ps.dart
Normal file
127
lib/data/model/container/ps.dart
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
|
|
||||||
|
abstract final class ContainerPs {
|
||||||
|
final String? id = null;
|
||||||
|
final String? image = null;
|
||||||
|
String? get name;
|
||||||
|
String? get cmd;
|
||||||
|
bool get running;
|
||||||
|
|
||||||
|
factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PodmanPs implements ContainerPs {
|
||||||
|
final List<String>? command;
|
||||||
|
final DateTime? created;
|
||||||
|
final bool? exited;
|
||||||
|
@override
|
||||||
|
final String? id;
|
||||||
|
@override
|
||||||
|
final String? image;
|
||||||
|
final List<String>? names;
|
||||||
|
final int? startedAt;
|
||||||
|
|
||||||
|
PodmanPs({
|
||||||
|
this.command,
|
||||||
|
this.created,
|
||||||
|
this.exited,
|
||||||
|
this.id,
|
||||||
|
this.image,
|
||||||
|
this.names,
|
||||||
|
this.startedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => names?.firstOrNull;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get cmd => command?.firstOrNull;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get running => exited != true;
|
||||||
|
|
||||||
|
factory PodmanPs.fromRawJson(String str) =>
|
||||||
|
PodmanPs.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory PodmanPs.fromJson(Map<String, dynamic> json) => PodmanPs(
|
||||||
|
command: json["Command"] == null
|
||||||
|
? []
|
||||||
|
: List<String>.from(json["Command"]!.map((x) => x)),
|
||||||
|
created:
|
||||||
|
json["Created"] == null ? null : DateTime.parse(json["Created"]),
|
||||||
|
exited: json["Exited"],
|
||||||
|
id: json["Id"],
|
||||||
|
image: json["Image"],
|
||||||
|
names: json["Names"] == null
|
||||||
|
? []
|
||||||
|
: List<String>.from(json["Names"]!.map((x) => x)),
|
||||||
|
startedAt: json["StartedAt"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"Command":
|
||||||
|
command == null ? [] : List<dynamic>.from(command!.map((x) => x)),
|
||||||
|
"Created": created?.toIso8601String(),
|
||||||
|
"Exited": exited,
|
||||||
|
"Id": id,
|
||||||
|
"Image": image,
|
||||||
|
"Names": names == null ? [] : List<dynamic>.from(names!.map((x) => x)),
|
||||||
|
"StartedAt": startedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DockerPs implements ContainerPs {
|
||||||
|
final String? command;
|
||||||
|
final String? createdAt;
|
||||||
|
@override
|
||||||
|
final String? id;
|
||||||
|
@override
|
||||||
|
final String? image;
|
||||||
|
final String? names;
|
||||||
|
final String? state;
|
||||||
|
|
||||||
|
DockerPs({
|
||||||
|
this.command,
|
||||||
|
this.createdAt,
|
||||||
|
this.id,
|
||||||
|
this.image,
|
||||||
|
this.names,
|
||||||
|
this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => names;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get cmd => command;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get running => state == 'running';
|
||||||
|
|
||||||
|
factory DockerPs.fromRawJson(String str) =>
|
||||||
|
DockerPs.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory DockerPs.fromJson(Map<String, dynamic> json) => DockerPs(
|
||||||
|
command: json["Command"],
|
||||||
|
createdAt: json["CreatedAt"],
|
||||||
|
id: json["ID"],
|
||||||
|
image: json["Image"],
|
||||||
|
names: json["Names"],
|
||||||
|
state: json["State"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"Command": command,
|
||||||
|
"CreatedAt": createdAt,
|
||||||
|
"ID": id,
|
||||||
|
"Image": image,
|
||||||
|
"Names": names,
|
||||||
|
"State": state,
|
||||||
|
};
|
||||||
|
}
|
||||||
18
lib/data/model/container/type.dart
Normal file
18
lib/data/model/container/type.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:toolbox/data/model/container/image.dart';
|
||||||
|
import 'package:toolbox/data/model/container/ps.dart';
|
||||||
|
|
||||||
|
enum ContainerType {
|
||||||
|
docker,
|
||||||
|
podman,
|
||||||
|
;
|
||||||
|
|
||||||
|
ContainerPs Function(String str) get ps => switch (this) {
|
||||||
|
ContainerType.docker => DockerPs.fromRawJson,
|
||||||
|
ContainerType.podman => PodmanPs.fromRawJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerImg Function(String str) get img => switch (this) {
|
||||||
|
ContainerType.docker => DockerImg.fromRawJson,
|
||||||
|
ContainerType.podman => PodmanImg.fromRawJson,
|
||||||
|
};
|
||||||
|
}
|
||||||
69
lib/data/model/container/version.dart
Normal file
69
lib/data/model/container/version.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class Containerd {
|
||||||
|
final ContainerdClient client;
|
||||||
|
|
||||||
|
Containerd({
|
||||||
|
required this.client,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Containerd.fromRawJson(String str) => Containerd.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory Containerd.fromJson(Map<String, dynamic> json) => Containerd(
|
||||||
|
client: ContainerdClient.fromJson(json["Client"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"Client": client.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerdClient {
|
||||||
|
final String apiVersion;
|
||||||
|
final String version;
|
||||||
|
final String goVersion;
|
||||||
|
final String gitCommit;
|
||||||
|
final String builtTime;
|
||||||
|
final int built;
|
||||||
|
final String osArch;
|
||||||
|
final String os;
|
||||||
|
|
||||||
|
ContainerdClient({
|
||||||
|
required this.apiVersion,
|
||||||
|
required this.version,
|
||||||
|
required this.goVersion,
|
||||||
|
required this.gitCommit,
|
||||||
|
required this.builtTime,
|
||||||
|
required this.built,
|
||||||
|
required this.osArch,
|
||||||
|
required this.os,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ContainerdClient.fromRawJson(String str) => ContainerdClient.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory ContainerdClient.fromJson(Map<String, dynamic> json) => ContainerdClient(
|
||||||
|
apiVersion: json["APIVersion"],
|
||||||
|
version: json["Version"],
|
||||||
|
goVersion: json["GoVersion"],
|
||||||
|
gitCommit: json["GitCommit"],
|
||||||
|
builtTime: json["BuiltTime"],
|
||||||
|
built: json["Built"],
|
||||||
|
osArch: json["OsArch"],
|
||||||
|
os: json["Os"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"APIVersion": apiVersion,
|
||||||
|
"Version": version,
|
||||||
|
"GoVersion": goVersion,
|
||||||
|
"GitCommit": gitCommit,
|
||||||
|
"BuiltTime": builtTime,
|
||||||
|
"Built": built,
|
||||||
|
"OsArch": osArch,
|
||||||
|
"Os": os,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
final _dockerImageReg = RegExp(r'(\S+) +(\S+) +(\S+) +(.+) +(\S+)');
|
|
||||||
|
|
||||||
class DockerImage {
|
|
||||||
final String repo;
|
|
||||||
final String tag;
|
|
||||||
final String id;
|
|
||||||
final String created;
|
|
||||||
final String size;
|
|
||||||
|
|
||||||
static final Map<String, DockerImage> _cache = <String, DockerImage>{};
|
|
||||||
|
|
||||||
DockerImage({
|
|
||||||
required this.repo,
|
|
||||||
required this.tag,
|
|
||||||
required this.id,
|
|
||||||
required this.created,
|
|
||||||
required this.size,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'repo': repo,
|
|
||||||
'tag': tag,
|
|
||||||
'id': id,
|
|
||||||
'created': created,
|
|
||||||
'size': size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory DockerImage.fromRawStr(String raw) {
|
|
||||||
return _cache.putIfAbsent(raw, () => _parse(raw));
|
|
||||||
}
|
|
||||||
|
|
||||||
static DockerImage _parse(String raw) {
|
|
||||||
final match = _dockerImageReg.firstMatch(raw);
|
|
||||||
if (match == null) {
|
|
||||||
throw Exception('Invalid docker image: $raw');
|
|
||||||
}
|
|
||||||
return DockerImage(
|
|
||||||
repo: match.group(1)!,
|
|
||||||
tag: match.group(2)!,
|
|
||||||
id: match.group(3)!,
|
|
||||||
created: match.group(4)!,
|
|
||||||
size: match.group(5)!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
final _seperator = RegExp(' +');
|
|
||||||
|
|
||||||
class DockerPsItem {
|
|
||||||
late String containerId;
|
|
||||||
late String image;
|
|
||||||
late String command;
|
|
||||||
late String created;
|
|
||||||
late String status;
|
|
||||||
late String ports;
|
|
||||||
late String name;
|
|
||||||
// String? cpu;
|
|
||||||
// String? mem;
|
|
||||||
// String? net;
|
|
||||||
// String? disk;
|
|
||||||
|
|
||||||
DockerPsItem(
|
|
||||||
this.containerId,
|
|
||||||
this.image,
|
|
||||||
this.command,
|
|
||||||
this.created,
|
|
||||||
this.status,
|
|
||||||
this.ports,
|
|
||||||
this.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
DockerPsItem.fromRawString(String rawString) {
|
|
||||||
List<String> parts = rawString.split(_seperator);
|
|
||||||
parts = parts.map((e) => e.trim()).toList();
|
|
||||||
|
|
||||||
containerId = parts[0];
|
|
||||||
image = parts[1];
|
|
||||||
command = parts[2].trim();
|
|
||||||
created = parts[3];
|
|
||||||
status = parts[4];
|
|
||||||
if (running && parts.length > 6) {
|
|
||||||
ports = parts[5];
|
|
||||||
name = parts[6];
|
|
||||||
} else {
|
|
||||||
ports = '';
|
|
||||||
name = parts[5];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// void parseStats(String rawString) {
|
|
||||||
// if (rawString.isEmpty) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// final parts = rawString.split(_seperator);
|
|
||||||
// if (parts.length != 8) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// cpu = parts[2];
|
|
||||||
// mem = parts[3];
|
|
||||||
// net = parts[5];
|
|
||||||
// disk = parts[6];
|
|
||||||
// }
|
|
||||||
|
|
||||||
bool get running => status.contains('Up ');
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'DockerPsItem<$containerId@$name>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
213
lib/data/provider/container.dart
Normal file
213
lib/data/provider/container.dart
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||||
|
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||||
|
import 'package:toolbox/data/model/container/image.dart';
|
||||||
|
import 'package:toolbox/data/model/container/ps.dart';
|
||||||
|
import 'package:toolbox/data/model/app/error.dart';
|
||||||
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
|
import 'package:toolbox/data/model/container/version.dart';
|
||||||
|
import 'package:toolbox/data/res/logger.dart';
|
||||||
|
import 'package:toolbox/data/res/store.dart';
|
||||||
|
|
||||||
|
final _dockerNotFound = RegExp(r'command not found|Unknown command');
|
||||||
|
|
||||||
|
class ContainerProvider extends ChangeNotifier {
|
||||||
|
SSHClient? client;
|
||||||
|
String? userName;
|
||||||
|
List<ContainerPs>? items;
|
||||||
|
List<ContainerImg>? images;
|
||||||
|
String? version;
|
||||||
|
ContainerErr? error;
|
||||||
|
String? hostId;
|
||||||
|
String? runLog;
|
||||||
|
BuildContext? context;
|
||||||
|
ContainerType type;
|
||||||
|
|
||||||
|
ContainerProvider({
|
||||||
|
this.client,
|
||||||
|
this.userName,
|
||||||
|
this.hostId,
|
||||||
|
this.context,
|
||||||
|
}) : type = Stores.docker.getType(hostId) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setType(ContainerType type) async {
|
||||||
|
this.type = type;
|
||||||
|
Stores.docker.setType(hostId, type);
|
||||||
|
error = runLog = items = images = version = null;
|
||||||
|
notifyListeners();
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
var raw = '';
|
||||||
|
await client?.execWithPwd(
|
||||||
|
_wrap(ContainerCmdType.execAll(type)),
|
||||||
|
context: context,
|
||||||
|
onStdout: (data, _) => raw = '$raw$data',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (raw.contains(_dockerNotFound)) {
|
||||||
|
error = ContainerErr(type: ContainerErrType.notInstalled);
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check result segments count
|
||||||
|
final segments = raw.split(seperator);
|
||||||
|
if (segments.length != ContainerCmdType.values.length) {
|
||||||
|
error = ContainerErr(
|
||||||
|
type: ContainerErrType.segmentsNotMatch,
|
||||||
|
message: 'Container segments: ${segments.length}',
|
||||||
|
);
|
||||||
|
Loggers.parse.warning('Container segments: ${segments.length}\n$raw');
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse docker version
|
||||||
|
final verRaw = ContainerCmdType.version.find(segments);
|
||||||
|
try {
|
||||||
|
final containerVersion = Containerd.fromRawJson(verRaw);
|
||||||
|
version = containerVersion.client.version;
|
||||||
|
} catch (e, trace) {
|
||||||
|
error = ContainerErr(
|
||||||
|
type: ContainerErrType.invalidVersion,
|
||||||
|
message: '$e',
|
||||||
|
);
|
||||||
|
Loggers.parse.warning('Container version failed', e, trace);
|
||||||
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse docker ps
|
||||||
|
final psRaw = ContainerCmdType.ps.find(segments);
|
||||||
|
|
||||||
|
final lines = psRaw.split('\n');
|
||||||
|
lines.removeWhere((element) => element.isEmpty);
|
||||||
|
if (lines.isNotEmpty) lines.removeAt(0);
|
||||||
|
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList();
|
||||||
|
|
||||||
|
// Parse docker images
|
||||||
|
final imageRaw = ContainerCmdType.images.find(segments);
|
||||||
|
try {
|
||||||
|
final imgLines = imageRaw.split('\n');
|
||||||
|
imgLines.removeWhere((element) => element.isEmpty);
|
||||||
|
if (imgLines.isNotEmpty) imgLines.removeAt(0);
|
||||||
|
images = imgLines.map((e) => ContainerImg.fromRawJson(e, type)).toList();
|
||||||
|
} catch (e, trace) {
|
||||||
|
error = ContainerErr(
|
||||||
|
type: ContainerErrType.parseImages,
|
||||||
|
message: '$e',
|
||||||
|
);
|
||||||
|
Loggers.parse.warning('Container images failed', e, trace);
|
||||||
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse docker stats
|
||||||
|
// final statsRaw = DockerCmdType.stats.find(segments);
|
||||||
|
// try {
|
||||||
|
// final statsLines = statsRaw.split('\n');
|
||||||
|
// statsLines.removeWhere((element) => element.isEmpty);
|
||||||
|
// if (statsLines.isNotEmpty) statsLines.removeAt(0);
|
||||||
|
// for (var item in items!) {
|
||||||
|
// final statsLine = statsLines.firstWhere(
|
||||||
|
// (element) => element.contains(item.containerId),
|
||||||
|
// orElse: () => '',
|
||||||
|
// );
|
||||||
|
// if (statsLine.isEmpty) continue;
|
||||||
|
// item.parseStats(statsLine);
|
||||||
|
// }
|
||||||
|
// } catch (e, trace) {
|
||||||
|
// error = DockerErr(
|
||||||
|
// type: DockerErrType.parseStats,
|
||||||
|
// message: '$e',
|
||||||
|
// );
|
||||||
|
// _logger.warning('Parse docker stats: $statsRaw', e, trace);
|
||||||
|
// } finally {
|
||||||
|
// notifyListeners();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ContainerErr?> stop(String id) async => await run('stop $id');
|
||||||
|
|
||||||
|
Future<ContainerErr?> start(String id) async => await run('start $id');
|
||||||
|
|
||||||
|
Future<ContainerErr?> delete(String id, bool force) async {
|
||||||
|
if (force) {
|
||||||
|
return await run('rm -f $id');
|
||||||
|
}
|
||||||
|
return await run('rm $id');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ContainerErr?> restart(String id) async => await run('restart $id');
|
||||||
|
|
||||||
|
Future<ContainerErr?> run(String cmd) async {
|
||||||
|
cmd = switch (type) {
|
||||||
|
ContainerType.docker => 'docker $cmd',
|
||||||
|
ContainerType.podman => 'podman $cmd',
|
||||||
|
};
|
||||||
|
|
||||||
|
runLog = '';
|
||||||
|
final errs = <String>[];
|
||||||
|
final code = await client?.execWithPwd(
|
||||||
|
_wrap(cmd),
|
||||||
|
context: context,
|
||||||
|
onStdout: (data, _) {
|
||||||
|
runLog = '$runLog$data';
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
onStderr: (data, _) => errs.add(data),
|
||||||
|
);
|
||||||
|
runLog = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
if (code != 0) {
|
||||||
|
return ContainerErr(
|
||||||
|
type: ContainerErrType.unknown,
|
||||||
|
message: errs.join('\n').trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await refresh();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// wrap cmd with `docker host`
|
||||||
|
String _wrap(String cmd) {
|
||||||
|
final dockerHost = Stores.docker.fetch(hostId);
|
||||||
|
cmd = 'export LANG=en_US.UTF-8 && $cmd';
|
||||||
|
final noDockerHost = dockerHost?.isEmpty ?? true;
|
||||||
|
if (!noDockerHost) {
|
||||||
|
cmd = 'export DOCKER_HOST=$dockerHost && $cmd';
|
||||||
|
}
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _jsonFmt = '--format "{{json .}}"';
|
||||||
|
|
||||||
|
enum ContainerCmdType {
|
||||||
|
version,
|
||||||
|
ps,
|
||||||
|
//stats,
|
||||||
|
images,
|
||||||
|
;
|
||||||
|
|
||||||
|
String exec(ContainerType type) {
|
||||||
|
final prefix = type.name;
|
||||||
|
return switch (this) {
|
||||||
|
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
||||||
|
ContainerCmdType.ps => '$prefix ps -a $_jsonFmt',
|
||||||
|
// DockerCmdType.stats => '$prefix stats --no-stream';
|
||||||
|
ContainerCmdType.images => '$prefix image ls $_jsonFmt',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static String execAll(ContainerType type) =>
|
||||||
|
values.map((e) => e.exec(type)).join(' && echo $seperator && ');
|
||||||
|
}
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
|
||||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
|
||||||
import 'package:toolbox/data/model/docker/image.dart';
|
|
||||||
import 'package:toolbox/data/model/docker/ps.dart';
|
|
||||||
import 'package:toolbox/data/model/app/error.dart';
|
|
||||||
import 'package:toolbox/data/res/logger.dart';
|
|
||||||
import 'package:toolbox/data/res/store.dart';
|
|
||||||
|
|
||||||
final _dockerNotFound = RegExp(r'command not found|Unknown command');
|
|
||||||
final _versionReg = RegExp(r'(Version:)\s+([0-9]+\.[0-9]+\.[0-9]+)');
|
|
||||||
// eg: `Docker Engine - Community`
|
|
||||||
final _editionReg = RegExp(r'Docker Engine - [a-zA-Z]+');
|
|
||||||
final _dockerPrefixReg = RegExp(r'(sudo )?docker ');
|
|
||||||
|
|
||||||
class DockerProvider extends ChangeNotifier {
|
|
||||||
SSHClient? client;
|
|
||||||
String? userName;
|
|
||||||
List<DockerPsItem>? items;
|
|
||||||
List<DockerImage>? images;
|
|
||||||
String? version;
|
|
||||||
String? edition;
|
|
||||||
DockerErr? error;
|
|
||||||
String? hostId;
|
|
||||||
String? runLog;
|
|
||||||
BuildContext? context;
|
|
||||||
|
|
||||||
void init(
|
|
||||||
SSHClient client,
|
|
||||||
String userName,
|
|
||||||
PwdRequestFunc onPwdReq,
|
|
||||||
String hostId,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
this.client = client;
|
|
||||||
this.userName = userName;
|
|
||||||
this.context = context;
|
|
||||||
this.hostId = hostId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
client = userName = error = items = version = edition = context = null;
|
|
||||||
hostId = runLog = images = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
var raw = '';
|
|
||||||
await client?.execWithPwd(
|
|
||||||
_wrap(DockerCmdType.execAll),
|
|
||||||
context: context,
|
|
||||||
onStdout: (data, _) => raw = '$raw$data',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (raw.contains(_dockerNotFound)) {
|
|
||||||
error = DockerErr(type: DockerErrType.notInstalled);
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check result segments count
|
|
||||||
final segments = raw.split(seperator);
|
|
||||||
if (segments.length != DockerCmdType.values.length) {
|
|
||||||
error = DockerErr(type: DockerErrType.segmentsNotMatch);
|
|
||||||
Loggers.parse.warning('Docker segments: ${segments.length}\n$raw');
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse docker version
|
|
||||||
final verRaw = DockerCmdType.version.find(segments);
|
|
||||||
version = _versionReg.firstMatch(verRaw)?.group(2);
|
|
||||||
edition = _editionReg.firstMatch(verRaw)?.group(0);
|
|
||||||
|
|
||||||
// Parse docker ps
|
|
||||||
final psRaw = DockerCmdType.ps.find(segments);
|
|
||||||
try {
|
|
||||||
final lines = psRaw.split('\n');
|
|
||||||
lines.removeWhere((element) => element.isEmpty);
|
|
||||||
if (lines.isNotEmpty) lines.removeAt(0);
|
|
||||||
items = lines.map((e) => DockerPsItem.fromRawString(e)).toList();
|
|
||||||
} catch (e, trace) {
|
|
||||||
error = DockerErr(
|
|
||||||
type: DockerErrType.parsePsItem,
|
|
||||||
message: '$psRaw\n-\n$e',
|
|
||||||
);
|
|
||||||
Loggers.parse.warning('Docker ps failed', e, trace);
|
|
||||||
} finally {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse docker images
|
|
||||||
final imageRaw = DockerCmdType.images.find(segments);
|
|
||||||
try {
|
|
||||||
final imageLines = imageRaw.split('\n');
|
|
||||||
imageLines.removeWhere((element) => element.isEmpty);
|
|
||||||
if (imageLines.isNotEmpty) imageLines.removeAt(0);
|
|
||||||
images = imageLines.map((e) => DockerImage.fromRawStr(e)).toList();
|
|
||||||
} catch (e, trace) {
|
|
||||||
error = DockerErr(
|
|
||||||
type: DockerErrType.parseImages,
|
|
||||||
message: '$imageRaw\n-\n$e',
|
|
||||||
);
|
|
||||||
Loggers.parse.warning('Docker images failed', e, trace);
|
|
||||||
} finally {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse docker stats
|
|
||||||
// final statsRaw = DockerCmdType.stats.find(segments);
|
|
||||||
// try {
|
|
||||||
// final statsLines = statsRaw.split('\n');
|
|
||||||
// statsLines.removeWhere((element) => element.isEmpty);
|
|
||||||
// if (statsLines.isNotEmpty) statsLines.removeAt(0);
|
|
||||||
// for (var item in items!) {
|
|
||||||
// final statsLine = statsLines.firstWhere(
|
|
||||||
// (element) => element.contains(item.containerId),
|
|
||||||
// orElse: () => '',
|
|
||||||
// );
|
|
||||||
// if (statsLine.isEmpty) continue;
|
|
||||||
// item.parseStats(statsLine);
|
|
||||||
// }
|
|
||||||
// } catch (e, trace) {
|
|
||||||
// error = DockerErr(
|
|
||||||
// type: DockerErrType.parseStats,
|
|
||||||
// message: '$statsRaw\n-\n$e',
|
|
||||||
// );
|
|
||||||
// _logger.warning('Parse docker stats: $statsRaw', e, trace);
|
|
||||||
// } finally {
|
|
||||||
// notifyListeners();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DockerErr?> stop(String id) async => await run('docker stop $id');
|
|
||||||
|
|
||||||
Future<DockerErr?> start(String id) async => await run('docker start $id');
|
|
||||||
|
|
||||||
Future<DockerErr?> delete(String id, bool force) async {
|
|
||||||
if (force) {
|
|
||||||
return await run('docker rm -f $id');
|
|
||||||
}
|
|
||||||
return await run('docker rm $id');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DockerErr?> restart(String id) async =>
|
|
||||||
await run('docker restart $id');
|
|
||||||
|
|
||||||
Future<DockerErr?> run(String cmd) async {
|
|
||||||
if (!cmd.startsWith(_dockerPrefixReg)) {
|
|
||||||
return DockerErr(type: DockerErrType.cmdNoPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
runLog = '';
|
|
||||||
final errs = <String>[];
|
|
||||||
final code = await client?.execWithPwd(
|
|
||||||
_wrap(cmd),
|
|
||||||
context: context,
|
|
||||||
onStdout: (data, _) {
|
|
||||||
runLog = '$runLog$data';
|
|
||||||
notifyListeners();
|
|
||||||
},
|
|
||||||
onStderr: (data, _) => errs.add(data),
|
|
||||||
);
|
|
||||||
runLog = null;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
if (code != 0) {
|
|
||||||
return DockerErr(
|
|
||||||
type: DockerErrType.unknown,
|
|
||||||
message: errs.join('\n').trim(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await refresh();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// wrap cmd with `docker host`
|
|
||||||
String _wrap(String cmd) {
|
|
||||||
final dockerHost = Stores.docker.fetch(hostId);
|
|
||||||
cmd = 'export LANG=en_US.UTF-8 && $cmd';
|
|
||||||
final noDockerHost = dockerHost?.isEmpty ?? true;
|
|
||||||
if (!noDockerHost) {
|
|
||||||
cmd = 'export DOCKER_HOST=$dockerHost && $cmd';
|
|
||||||
}
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
class BuildData {
|
class BuildData {
|
||||||
static const String name = "ServerBox";
|
static const String name = "ServerBox";
|
||||||
static const int build = 707;
|
static const int build = 709;
|
||||||
static const String engine = "3.16.7";
|
static const String engine = "3.16.7";
|
||||||
static const String buildAt = "2024-01-16 12:17:21";
|
static const String buildAt = "2024-01-19 17:32:15";
|
||||||
static const int modifications = 1;
|
static const int modifications = 2;
|
||||||
static const int script = 34;
|
static const int script = 34;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:toolbox/data/provider/app.dart';
|
import 'package:toolbox/data/provider/app.dart';
|
||||||
import 'package:toolbox/data/provider/debug.dart';
|
import 'package:toolbox/data/provider/debug.dart';
|
||||||
import 'package:toolbox/data/provider/docker.dart';
|
|
||||||
import 'package:toolbox/data/provider/private_key.dart';
|
import 'package:toolbox/data/provider/private_key.dart';
|
||||||
import 'package:toolbox/data/provider/server.dart';
|
import 'package:toolbox/data/provider/server.dart';
|
||||||
import 'package:toolbox/data/provider/sftp.dart';
|
import 'package:toolbox/data/provider/sftp.dart';
|
||||||
@@ -10,7 +9,6 @@ import 'package:toolbox/locator.dart';
|
|||||||
abstract final class Pros {
|
abstract final class Pros {
|
||||||
static final app = locator<AppProvider>();
|
static final app = locator<AppProvider>();
|
||||||
static final debug = locator<DebugProvider>();
|
static final debug = locator<DebugProvider>();
|
||||||
static final docker = locator<DockerProvider>();
|
|
||||||
static final key = locator<PrivateKeyProvider>();
|
static final key = locator<PrivateKeyProvider>();
|
||||||
static final server = locator<ServerProvider>();
|
static final server = locator<ServerProvider>();
|
||||||
static final sftp = locator<SftpProvider>();
|
static final sftp = locator<SftpProvider>();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:toolbox/core/persistant_store.dart';
|
import 'package:toolbox/core/persistant_store.dart';
|
||||||
import 'package:toolbox/data/store/docker.dart';
|
import 'package:toolbox/data/store/container.dart';
|
||||||
import 'package:toolbox/data/store/history.dart';
|
import 'package:toolbox/data/store/history.dart';
|
||||||
import 'package:toolbox/data/store/private_key.dart';
|
import 'package:toolbox/data/store/private_key.dart';
|
||||||
import 'package:toolbox/data/store/server.dart';
|
import 'package:toolbox/data/store/server.dart';
|
||||||
|
|||||||
30
lib/data/store/container.dart
Normal file
30
lib/data/store/container.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
|
|
||||||
|
import '../../core/persistant_store.dart';
|
||||||
|
|
||||||
|
const _keyConfig = 'providerConfig';
|
||||||
|
|
||||||
|
class DockerStore extends PersistentStore {
|
||||||
|
DockerStore() : super('docker');
|
||||||
|
|
||||||
|
String? fetch(String? id) {
|
||||||
|
return box.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(String id, String host) {
|
||||||
|
box.put(id, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerType getType([String? id]) {
|
||||||
|
final cfg = box.get(_keyConfig + (id ?? ''));
|
||||||
|
if (cfg == null) {
|
||||||
|
return ContainerType.docker;
|
||||||
|
} else {
|
||||||
|
return ContainerType.values.firstWhere((e) => e.toString() == cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setType(String? id, ContainerType type) {
|
||||||
|
box.put(_keyConfig + (id ?? ''), type.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import '../../core/persistant_store.dart';
|
|
||||||
|
|
||||||
class DockerStore extends PersistentStore {
|
|
||||||
DockerStore() : super('docker');
|
|
||||||
|
|
||||||
String? fetch(String? id) {
|
|
||||||
return box.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void put(String id, String host) {
|
|
||||||
box.put(id, host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht",
|
"collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht",
|
||||||
"conn": "Verbindung",
|
"conn": "Verbindung",
|
||||||
"connected": "in Verbindung gebracht",
|
"connected": "in Verbindung gebracht",
|
||||||
|
"container": "Container",
|
||||||
"containerName": "Container Name",
|
"containerName": "Container Name",
|
||||||
"containerStatus": "Container Status",
|
"containerStatus": "Container Status",
|
||||||
"convert": "Konvertieren",
|
"convert": "Konvertieren",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "Erfolgreich",
|
"success": "Erfolgreich",
|
||||||
"suspend": "Suspend",
|
"suspend": "Suspend",
|
||||||
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
|
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
|
||||||
|
"switchTo": "Wechseln zu {val}",
|
||||||
"syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.",
|
"syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.",
|
||||||
"system": "Systeme",
|
"system": "Systeme",
|
||||||
"tag": "Tags",
|
"tag": "Tags",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "Whether to collapse long lists present in the UI by default",
|
"collapseUITip": "Whether to collapse long lists present in the UI by default",
|
||||||
"conn": "Connection",
|
"conn": "Connection",
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
|
"container": "Container",
|
||||||
"containerName": "Container name",
|
"containerName": "Container name",
|
||||||
"containerStatus": "Container status",
|
"containerStatus": "Container status",
|
||||||
"convert": "Convert",
|
"convert": "Convert",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "Success",
|
"success": "Success",
|
||||||
"suspend": "Suspend",
|
"suspend": "Suspend",
|
||||||
"suspendTip": "The suspend function requires root privileges and systemd support.",
|
"suspendTip": "The suspend function requires root privileges and systemd support.",
|
||||||
|
"switchTo": "Switch to {val}",
|
||||||
"syncTip": "A restart may be required for some changes to take effect.",
|
"syncTip": "A restart may be required for some changes to take effect.",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"tag": "Tags",
|
"tag": "Tags",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "Réduction ou non des longues listes présentes dans l'interface utilisateur par défaut",
|
"collapseUITip": "Réduction ou non des longues listes présentes dans l'interface utilisateur par défaut",
|
||||||
"conn": "Connexion",
|
"conn": "Connexion",
|
||||||
"connected": "Connecté",
|
"connected": "Connecté",
|
||||||
|
"container": "Conteneurs",
|
||||||
"containerName": "Nom du conteneur",
|
"containerName": "Nom du conteneur",
|
||||||
"containerStatus": "Statut du conteneur",
|
"containerStatus": "Statut du conteneur",
|
||||||
"convert": "Convertir",
|
"convert": "Convertir",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"suspend": "Suspendre",
|
"suspend": "Suspendre",
|
||||||
"suspendTip": "La fonction de suspension nécessite des privilèges root et la prise en charge de systemd.",
|
"suspendTip": "La fonction de suspension nécessite des privilèges root et la prise en charge de systemd.",
|
||||||
|
"switchTo": "Passer à {val}",
|
||||||
"syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.",
|
"syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.",
|
||||||
"system": "Système",
|
"system": "Système",
|
||||||
"tag": "Étiquettes",
|
"tag": "Étiquettes",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak",
|
"collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak",
|
||||||
"conn": "Koneksi",
|
"conn": "Koneksi",
|
||||||
"connected": "Terhubung",
|
"connected": "Terhubung",
|
||||||
|
"container": "Wadah",
|
||||||
"containerName": "Nama kontainer",
|
"containerName": "Nama kontainer",
|
||||||
"containerStatus": "Status wadah",
|
"containerStatus": "Status wadah",
|
||||||
"convert": "Mengubah",
|
"convert": "Mengubah",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "Kesuksesan",
|
"success": "Kesuksesan",
|
||||||
"suspend": "Suspend",
|
"suspend": "Suspend",
|
||||||
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
|
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
|
||||||
|
"switchTo": "Beralih ke {val}",
|
||||||
"syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.",
|
"syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.",
|
||||||
"system": "Sistem",
|
"system": "Sistem",
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "是否默认折叠UI中存在的长列表",
|
"collapseUITip": "是否默认折叠UI中存在的长列表",
|
||||||
"conn": "连接",
|
"conn": "连接",
|
||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
|
"container": "容器",
|
||||||
"containerName": "容器名",
|
"containerName": "容器名",
|
||||||
"containerStatus": "容器状态",
|
"containerStatus": "容器状态",
|
||||||
"convert": "转换",
|
"convert": "转换",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"suspend": "挂起",
|
"suspend": "挂起",
|
||||||
"suspendTip": "suspend 功能需要 root 权限及 systemd 支持。",
|
"suspendTip": "suspend 功能需要 root 权限及 systemd 支持。",
|
||||||
|
"switchTo": "切换到 {val}",
|
||||||
"syncTip": "可能需要重新启动,某些更改才能生效。",
|
"syncTip": "可能需要重新启动,某些更改才能生效。",
|
||||||
"system": "系统",
|
"system": "系统",
|
||||||
"tag": "标签",
|
"tag": "标签",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"collapseUITip": "是否預設折疊UI中存在的長列表",
|
"collapseUITip": "是否預設折疊UI中存在的長列表",
|
||||||
"conn": "連接",
|
"conn": "連接",
|
||||||
"connected": "已連接",
|
"connected": "已連接",
|
||||||
|
"container": "容器",
|
||||||
"containerName": "容器名稱",
|
"containerName": "容器名稱",
|
||||||
"containerStatus": "容器狀態",
|
"containerStatus": "容器狀態",
|
||||||
"convert": "轉換",
|
"convert": "轉換",
|
||||||
@@ -220,6 +221,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"suspend": "挂起",
|
"suspend": "挂起",
|
||||||
"suspendTip": "suspend 功能需要 root 權限及 systemd 支持。",
|
"suspendTip": "suspend 功能需要 root 權限及 systemd 支持。",
|
||||||
|
"switchTo": "切換到 {val}",
|
||||||
"syncTip": "可能需要重新啟動,某些更改才能生效。",
|
"syncTip": "可能需要重新啟動,某些更改才能生效。",
|
||||||
"system": "系統",
|
"system": "系統",
|
||||||
"tag": "标签",
|
"tag": "标签",
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import 'package:get_it/get_it.dart';
|
|||||||
|
|
||||||
import 'data/provider/app.dart';
|
import 'data/provider/app.dart';
|
||||||
import 'data/provider/debug.dart';
|
import 'data/provider/debug.dart';
|
||||||
import 'data/provider/docker.dart';
|
import 'data/provider/container.dart';
|
||||||
import 'data/provider/private_key.dart';
|
import 'data/provider/private_key.dart';
|
||||||
import 'data/provider/server.dart';
|
import 'data/provider/server.dart';
|
||||||
import 'data/provider/sftp.dart';
|
import 'data/provider/sftp.dart';
|
||||||
import 'data/provider/snippet.dart';
|
import 'data/provider/snippet.dart';
|
||||||
import 'data/provider/virtual_keyboard.dart';
|
import 'data/provider/virtual_keyboard.dart';
|
||||||
import 'data/service/app.dart';
|
import 'data/service/app.dart';
|
||||||
import 'data/store/docker.dart';
|
import 'data/store/container.dart';
|
||||||
import 'data/store/history.dart';
|
import 'data/store/history.dart';
|
||||||
import 'data/store/private_key.dart';
|
import 'data/store/private_key.dart';
|
||||||
import 'data/store/server.dart';
|
import 'data/store/server.dart';
|
||||||
@@ -25,7 +25,7 @@ void _setupLocatorForServices() {
|
|||||||
void _setupLocatorForProviders() {
|
void _setupLocatorForProviders() {
|
||||||
locator.registerSingleton(AppProvider());
|
locator.registerSingleton(AppProvider());
|
||||||
locator.registerSingleton(DebugProvider());
|
locator.registerSingleton(DebugProvider());
|
||||||
locator.registerSingleton(DockerProvider());
|
locator.registerSingleton(ContainerProvider());
|
||||||
locator.registerSingleton(ServerProvider());
|
locator.registerSingleton(ServerProvider());
|
||||||
locator.registerSingleton(VirtKeyProvider());
|
locator.registerSingleton(VirtKeyProvider());
|
||||||
locator.registerSingleton(SnippetProvider());
|
locator.registerSingleton(SnippetProvider());
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import 'data/model/server/snippet.dart';
|
|||||||
import 'data/model/ssh/virtual_key.dart';
|
import 'data/model/ssh/virtual_key.dart';
|
||||||
import 'data/provider/app.dart';
|
import 'data/provider/app.dart';
|
||||||
import 'data/provider/debug.dart';
|
import 'data/provider/debug.dart';
|
||||||
import 'data/provider/docker.dart';
|
|
||||||
import 'data/provider/private_key.dart';
|
import 'data/provider/private_key.dart';
|
||||||
import 'data/provider/server.dart';
|
import 'data/provider/server.dart';
|
||||||
import 'data/provider/sftp.dart';
|
import 'data/provider/sftp.dart';
|
||||||
@@ -43,7 +42,6 @@ Future<void> main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => locator<AppProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<AppProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<DebugProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<DebugProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<DockerProvider>()),
|
|
||||||
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
||||||
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
||||||
|
|||||||
@@ -4,85 +4,79 @@ import 'package:toolbox/core/extension/context/common.dart';
|
|||||||
import 'package:toolbox/core/extension/context/dialog.dart';
|
import 'package:toolbox/core/extension/context/dialog.dart';
|
||||||
import 'package:toolbox/core/extension/context/locale.dart';
|
import 'package:toolbox/core/extension/context/locale.dart';
|
||||||
import 'package:toolbox/core/extension/context/snackbar.dart';
|
import 'package:toolbox/core/extension/context/snackbar.dart';
|
||||||
|
import 'package:toolbox/core/extension/stringx.dart';
|
||||||
import 'package:toolbox/core/route.dart';
|
import 'package:toolbox/core/route.dart';
|
||||||
import 'package:toolbox/data/model/docker/image.dart';
|
import 'package:toolbox/data/model/container/image.dart';
|
||||||
import 'package:toolbox/data/res/provider.dart';
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:toolbox/data/res/store.dart';
|
||||||
import 'package:toolbox/view/widget/expand_tile.dart';
|
import 'package:toolbox/view/widget/expand_tile.dart';
|
||||||
import 'package:toolbox/view/widget/input_field.dart';
|
import 'package:toolbox/view/widget/input_field.dart';
|
||||||
|
|
||||||
import '../../data/model/docker/ps.dart';
|
import '../../data/model/container/ps.dart';
|
||||||
import '../../data/model/server/server_private_info.dart';
|
import '../../data/model/server/server_private_info.dart';
|
||||||
import '../../data/provider/docker.dart';
|
import '../../data/provider/container.dart';
|
||||||
import '../../data/model/app/error.dart';
|
|
||||||
import '../../data/model/app/menu.dart';
|
import '../../data/model/app/menu.dart';
|
||||||
import '../../data/res/ui.dart';
|
import '../../data/res/ui.dart';
|
||||||
import '../../data/res/url.dart';
|
|
||||||
import '../widget/appbar.dart';
|
import '../widget/appbar.dart';
|
||||||
import '../widget/popup_menu.dart';
|
import '../widget/popup_menu.dart';
|
||||||
import '../widget/cardx.dart';
|
import '../widget/cardx.dart';
|
||||||
import '../widget/two_line_text.dart';
|
import '../widget/two_line_text.dart';
|
||||||
import '../widget/url_text.dart';
|
|
||||||
|
|
||||||
class DockerManagePage extends StatefulWidget {
|
class ContainerPage extends StatefulWidget {
|
||||||
final ServerPrivateInfo spi;
|
final ServerPrivateInfo spi;
|
||||||
const DockerManagePage({required this.spi, super.key});
|
const ContainerPage({required this.spi, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DockerManagePage> createState() => _DockerManagePageState();
|
State<ContainerPage> createState() => _ContainerPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DockerManagePageState extends State<DockerManagePage> {
|
class _ContainerPageState extends State<ContainerPage> {
|
||||||
final _textController = TextEditingController();
|
final _textController = TextEditingController();
|
||||||
final _docker = Pros.docker;
|
late final _container = ContainerProvider(
|
||||||
|
client: widget.spi.server?.client,
|
||||||
|
userName: widget.spi.user,
|
||||||
|
hostId: widget.spi.id,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_docker.clear();
|
|
||||||
_textController.dispose();
|
_textController.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final client = widget.spi.server?.client;
|
|
||||||
if (client == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_docker
|
|
||||||
..init(
|
|
||||||
client,
|
|
||||||
widget.spi.user,
|
|
||||||
(user) async => await context.showPwdDialog(user),
|
|
||||||
widget.spi.id,
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
..refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<DockerProvider>(builder: (_, ___, __) {
|
return ChangeNotifierProvider(
|
||||||
return Scaffold(
|
create: (_) => _container,
|
||||||
appBar: CustomAppBar(
|
builder: (_, __) => Consumer<ContainerProvider>(
|
||||||
centerTitle: true,
|
builder: (_, ___, __) {
|
||||||
title: TwoLineText(up: 'Docker', down: widget.spi.name),
|
return Scaffold(
|
||||||
actions: [
|
appBar: CustomAppBar(
|
||||||
IconButton(
|
centerTitle: true,
|
||||||
onPressed: () async {
|
title: TwoLineText(up: 'Container', down: widget.spi.name),
|
||||||
context.showLoadingDialog();
|
actions: [
|
||||||
await _docker.refresh();
|
IconButton(
|
||||||
context.pop();
|
onPressed: () async {
|
||||||
},
|
context.showLoadingDialog();
|
||||||
icon: const Icon(Icons.refresh),
|
await _container.refresh();
|
||||||
)
|
context.pop();
|
||||||
],
|
},
|
||||||
),
|
icon: const Icon(Icons.refresh),
|
||||||
body: _buildMain(),
|
)
|
||||||
floatingActionButton: _docker.error == null ? _buildFAB() : null,
|
],
|
||||||
);
|
),
|
||||||
});
|
body: _buildMain(),
|
||||||
|
floatingActionButton: _container.error == null ? _buildFAB() : null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFAB() {
|
Widget _buildFAB() {
|
||||||
@@ -92,6 +86,196 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildMain() {
|
||||||
|
if (_container.error != null && _container.items == null) {
|
||||||
|
return SizedBox.expand(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
const Icon(
|
||||||
|
Icons.error,
|
||||||
|
size: 37,
|
||||||
|
),
|
||||||
|
UIs.height13,
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 23),
|
||||||
|
child: Text(_container.error?.toString() ?? l10n.unknownError),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_buildEditHost(),
|
||||||
|
_buildSwitchProvider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
UIs.height13,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_container.items == null || _container.images == null) {
|
||||||
|
return UIs.centerLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
final items = <Widget>[
|
||||||
|
_buildLoading(),
|
||||||
|
_buildVersion(),
|
||||||
|
_buildPs(),
|
||||||
|
_buildImage(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_buildEditHost(),
|
||||||
|
_buildSwitchProvider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
].map((e) => CardX(child: e));
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(7),
|
||||||
|
children: items.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImage() {
|
||||||
|
return ExpandTile(
|
||||||
|
title: Text(l10n.imagesList),
|
||||||
|
subtitle: Text(
|
||||||
|
l10n.dockerImagesFmt(_container.images!.length),
|
||||||
|
style: UIs.textGrey,
|
||||||
|
),
|
||||||
|
initiallyExpanded: (_container.images?.length ?? 0) <= 3,
|
||||||
|
children: _container.images?.map(_buildImageItem).toList() ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImageItem(ContainerImg e) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(e.repository ?? l10n.unknown),
|
||||||
|
subtitle: Text('${e.tag} - ${e.sizeMB}', style: UIs.textGrey),
|
||||||
|
trailing: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () => _showImageRmDialog(e),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLoading() {
|
||||||
|
if (_container.runLog == null) return UIs.placeholder;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(17),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
UIs.height13,
|
||||||
|
Text(_container.runLog ?? '...'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVersion() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(17),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(_container.type.name.upperFirst),
|
||||||
|
Text(_container.version ?? l10n.unknown),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPs() {
|
||||||
|
final items = _container.items;
|
||||||
|
if (items == null) return UIs.placeholder;
|
||||||
|
return ExpandTile(
|
||||||
|
title: Text(l10n.containerStatus),
|
||||||
|
subtitle: Text(
|
||||||
|
_buildPsCardSubtitle(items),
|
||||||
|
style: UIs.textGrey,
|
||||||
|
),
|
||||||
|
initiallyExpanded: items.length <= 7,
|
||||||
|
children: items.map(_buildPsItem).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPsItem(ContainerPs item) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(item.name ?? l10n.unknown),
|
||||||
|
subtitle: Text(
|
||||||
|
item.image ?? l10n.unknown,
|
||||||
|
style: UIs.text13Grey,
|
||||||
|
),
|
||||||
|
trailing: _buildMoreBtn(item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMoreBtn(ContainerPs dItem) {
|
||||||
|
return PopupMenu(
|
||||||
|
items: ContainerMenu.items(dItem.running).map((e) => e.widget).toList(),
|
||||||
|
onSelected: (item) => _onTapMoreBtn(item, dItem),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _buildPsCardSubtitle(List<ContainerPs> running) {
|
||||||
|
final runningCount = running.where((element) => element.running).length;
|
||||||
|
final stoped = running.length - runningCount;
|
||||||
|
if (stoped == 0) {
|
||||||
|
return l10n.dockerStatusRunningFmt(runningCount);
|
||||||
|
}
|
||||||
|
return l10n.dockerStatusRunningAndStoppedFmt(runningCount, stoped);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEditHost() {
|
||||||
|
final children = <Widget>[];
|
||||||
|
final emptyImgs = _container.images?.isEmpty ?? false;
|
||||||
|
final emptyPs = _container.items?.isEmpty ?? false;
|
||||||
|
if (emptyPs && emptyImgs) {
|
||||||
|
children.add(Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
||||||
|
child: Text(
|
||||||
|
l10n.dockerEmptyRunningItems,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
children.add(
|
||||||
|
TextButton(
|
||||||
|
onPressed: _showEditHostDialog,
|
||||||
|
child: Text(l10n.dockerEditHost),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return Column(
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSwitchProvider() {
|
||||||
|
late final Widget child;
|
||||||
|
if (_container.type == ContainerType.podman) {
|
||||||
|
child = TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_container.setType(ContainerType.docker);
|
||||||
|
},
|
||||||
|
child: Text(l10n.switchTo('Docker')),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child = TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_container.setType(ContainerType.podman);
|
||||||
|
},
|
||||||
|
child: Text(l10n.switchTo('Podman')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _showAddFAB() async {
|
Future<void> _showAddFAB() async {
|
||||||
final imageCtrl = TextEditingController();
|
final imageCtrl = TextEditingController();
|
||||||
final nameCtrl = TextEditingController();
|
final nameCtrl = TextEditingController();
|
||||||
@@ -157,7 +341,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
context.pop();
|
context.pop();
|
||||||
context.showLoadingDialog();
|
context.showLoadingDialog();
|
||||||
final result = await _docker.run(cmd);
|
final result = await _container.run(cmd);
|
||||||
context.pop();
|
context.pop();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
context.showSnackBar(result.message ?? l10n.unknownError);
|
context.showSnackBar(result.message ?? l10n.unknownError);
|
||||||
@@ -177,336 +361,9 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
suffix = '$args $image';
|
suffix = '$args $image';
|
||||||
}
|
}
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
return 'docker run -itd $suffix';
|
return 'run -itd $suffix';
|
||||||
}
|
}
|
||||||
return 'docker run -itd --name $name $suffix';
|
return 'run -itd --name $name $suffix';
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMain() {
|
|
||||||
if (_docker.error != null && _docker.items == null) {
|
|
||||||
return SizedBox.expand(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.error,
|
|
||||||
size: 37,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 27),
|
|
||||||
Text(_docker.error?.message ?? l10n.unknownError),
|
|
||||||
const SizedBox(height: 27),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(17),
|
|
||||||
child: _buildSolution(_docker.error!),
|
|
||||||
),
|
|
||||||
_buildEditHost(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (_docker.items == null || _docker.images == null) {
|
|
||||||
return UIs.centerLoading;
|
|
||||||
}
|
|
||||||
|
|
||||||
final items = <Widget>[
|
|
||||||
_buildLoading(),
|
|
||||||
_buildVersion(),
|
|
||||||
_buildPs(),
|
|
||||||
_buildImage(),
|
|
||||||
_buildEditHost(),
|
|
||||||
].map((e) => CardX(child: e));
|
|
||||||
return ListView(
|
|
||||||
padding: const EdgeInsets.all(7),
|
|
||||||
children: items.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildImage() {
|
|
||||||
return ExpandTile(
|
|
||||||
title: Text(l10n.imagesList),
|
|
||||||
subtitle: Text(
|
|
||||||
l10n.dockerImagesFmt(_docker.images!.length),
|
|
||||||
style: UIs.textGrey,
|
|
||||||
),
|
|
||||||
initiallyExpanded: (_docker.images?.length ?? 0) <= 3,
|
|
||||||
children: _docker.images?.map(_buildImageItem).toList() ?? [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildImageItem(DockerImage e) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text(e.repo),
|
|
||||||
subtitle: Text('${e.tag} - ${e.size}', style: UIs.textGrey),
|
|
||||||
trailing: IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () => _showImageRmDialog(e),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showImageRmDialog(DockerImage e) {
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.attention),
|
|
||||||
child: Text(l10n.askContinue('${l10n.delete} Image(${e.repo})')),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: Text(l10n.cancel),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
context.pop();
|
|
||||||
final result = await Pros.docker.run(
|
|
||||||
'docker rmi ${e.id} -f',
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
context.showSnackBar(result.message ?? l10n.unknownError);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(l10n.ok, style: UIs.textRed),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildLoading() {
|
|
||||||
if (Pros.docker.runLog == null) return UIs.placeholder;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(17),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
UIs.height13,
|
|
||||||
Text(_docker.runLog ?? '...'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSolution(DockerErr err) {
|
|
||||||
switch (err.type) {
|
|
||||||
case DockerErrType.notInstalled:
|
|
||||||
return UrlText(
|
|
||||||
text: l10n.installDockerWithUrl,
|
|
||||||
replace: l10n.install,
|
|
||||||
);
|
|
||||||
case DockerErrType.noClient:
|
|
||||||
return Text(l10n.waitConnection);
|
|
||||||
case DockerErrType.invalidVersion:
|
|
||||||
return UrlText(
|
|
||||||
text: l10n.invalidVersionHelp(Urls.appHelp),
|
|
||||||
replace: 'Github',
|
|
||||||
);
|
|
||||||
case DockerErrType.parseImages:
|
|
||||||
return const Text('Parse images error');
|
|
||||||
case DockerErrType.parsePsItem:
|
|
||||||
return const Text('Parse ps item error');
|
|
||||||
case DockerErrType.parseStats:
|
|
||||||
return const Text('Parse stats error');
|
|
||||||
case DockerErrType.unknown:
|
|
||||||
return const Text('Unknown error');
|
|
||||||
case DockerErrType.cmdNoPrefix:
|
|
||||||
return const Text('Cmd no prefix');
|
|
||||||
case DockerErrType.segmentsNotMatch:
|
|
||||||
return const Text('Segments not match');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVersion() {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(17),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(_docker.edition ?? l10n.unknown),
|
|
||||||
Text(_docker.version ?? l10n.unknown),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPs() {
|
|
||||||
final items = Pros.docker.items;
|
|
||||||
if (items == null) return UIs.placeholder;
|
|
||||||
return ExpandTile(
|
|
||||||
title: Text(l10n.containerStatus),
|
|
||||||
subtitle: Text(
|
|
||||||
_buildPsCardSubtitle(items),
|
|
||||||
style: UIs.textGrey,
|
|
||||||
),
|
|
||||||
initiallyExpanded: items.length <= 7,
|
|
||||||
children: items.map(_buildPsItem).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPsItem(DockerPsItem item) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text(item.image),
|
|
||||||
subtitle: Text(
|
|
||||||
'${item.name} - ${item.status}',
|
|
||||||
style: UIs.text13Grey,
|
|
||||||
),
|
|
||||||
trailing: _buildMoreBtn(item),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMoreBtn(DockerPsItem dItem) {
|
|
||||||
return PopupMenu(
|
|
||||||
items: DockerMenuType.items(dItem.running).map((e) => e.widget).toList(),
|
|
||||||
onSelected: (item) async {
|
|
||||||
switch (item) {
|
|
||||||
case DockerMenuType.rm:
|
|
||||||
var force = false;
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.attention),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(l10n.askContinue(
|
|
||||||
'${l10n.delete} Container(${dItem.name})',
|
|
||||||
)),
|
|
||||||
UIs.height13,
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatefulBuilder(builder: (_, setState) {
|
|
||||||
return Checkbox(
|
|
||||||
value: force,
|
|
||||||
onChanged: (val) => setState(
|
|
||||||
() => force = val ?? false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
Text(l10n.force),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
context.pop();
|
|
||||||
context.showLoadingDialog();
|
|
||||||
final result = await _docker.delete(
|
|
||||||
dItem.containerId,
|
|
||||||
force,
|
|
||||||
);
|
|
||||||
context.pop();
|
|
||||||
if (result != null) {
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.error),
|
|
||||||
child: Text(result.message ?? l10n.unknownError),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(l10n.ok),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case DockerMenuType.start:
|
|
||||||
context.showLoadingDialog();
|
|
||||||
final result = await _docker.start(dItem.containerId);
|
|
||||||
context.pop();
|
|
||||||
if (result != null) {
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.error),
|
|
||||||
child: Text(result.message ?? l10n.unknownError),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DockerMenuType.stop:
|
|
||||||
context.showLoadingDialog();
|
|
||||||
final result = await _docker.stop(dItem.containerId);
|
|
||||||
context.pop();
|
|
||||||
if (result != null) {
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.error),
|
|
||||||
child: Text(result.message ?? l10n.unknownError),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DockerMenuType.restart:
|
|
||||||
context.showLoadingDialog();
|
|
||||||
final result = await _docker.restart(dItem.containerId);
|
|
||||||
context.pop();
|
|
||||||
if (result != null) {
|
|
||||||
context.showRoundDialog(
|
|
||||||
title: Text(l10n.error),
|
|
||||||
child: Text(result.message ?? l10n.unknownError),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DockerMenuType.logs:
|
|
||||||
AppRoute.ssh(
|
|
||||||
spi: widget.spi,
|
|
||||||
initCmd: 'docker logs -f --tail 100 ${dItem.containerId}',
|
|
||||||
).go(context);
|
|
||||||
break;
|
|
||||||
case DockerMenuType.terminal:
|
|
||||||
AppRoute.ssh(
|
|
||||||
spi: widget.spi,
|
|
||||||
initCmd: 'docker exec -it ${dItem.containerId} sh',
|
|
||||||
).go(context);
|
|
||||||
break;
|
|
||||||
// case DockerMenuType.stats:
|
|
||||||
// showRoundDialog(
|
|
||||||
// context: context,
|
|
||||||
// title: Text(l10n.stats),
|
|
||||||
// child: Text(
|
|
||||||
// 'CPU: ${dItem.cpu}\n'
|
|
||||||
// 'Mem: ${dItem.mem}\n'
|
|
||||||
// 'Net: ${dItem.net}\n'
|
|
||||||
// 'Block: ${dItem.disk}',
|
|
||||||
// ),
|
|
||||||
// actions: [
|
|
||||||
// TextButton(
|
|
||||||
// onPressed: () => context.pop(),
|
|
||||||
// child: Text(l10n.ok),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _buildPsCardSubtitle(List<DockerPsItem> running) {
|
|
||||||
final runningCount = running.where((element) => element.running).length;
|
|
||||||
final stoped = running.length - runningCount;
|
|
||||||
if (stoped == 0) {
|
|
||||||
return l10n.dockerStatusRunningFmt(runningCount);
|
|
||||||
}
|
|
||||||
return l10n.dockerStatusRunningAndStoppedFmt(runningCount, stoped);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEditHost() {
|
|
||||||
final children = <Widget>[];
|
|
||||||
final emptyImgs = _docker.images?.isEmpty ?? false;
|
|
||||||
final emptyPs = _docker.items?.isEmpty ?? false;
|
|
||||||
if (emptyPs && emptyImgs) {
|
|
||||||
children.add(Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
|
||||||
child: Text(
|
|
||||||
l10n.dockerEmptyRunningItems,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
children.add(
|
|
||||||
TextButton(
|
|
||||||
onPressed: _showEditHostDialog,
|
|
||||||
child: Text(l10n.dockerEditHost),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return Column(
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showEditHostDialog() async {
|
Future<void> _showEditHostDialog() async {
|
||||||
@@ -533,6 +390,147 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
void _onSaveDockerHost(String val) {
|
void _onSaveDockerHost(String val) {
|
||||||
context.pop();
|
context.pop();
|
||||||
Stores.docker.put(widget.spi.id, val.trim());
|
Stores.docker.put(widget.spi.id, val.trim());
|
||||||
_docker.refresh();
|
_container.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showImageRmDialog(ContainerImg e) {
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.attention),
|
||||||
|
child: Text(l10n.askContinue('${l10n.delete} Image(${e.repository})')),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
context.pop();
|
||||||
|
final result = await _container.run('rmi ${e.id} -f');
|
||||||
|
if (result != null) {
|
||||||
|
context.showSnackBar(result.message ?? l10n.unknownError);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(l10n.ok, style: UIs.textRed),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onTapMoreBtn(ContainerMenu item, ContainerPs dItem) async {
|
||||||
|
final id = dItem.id;
|
||||||
|
if (id == null) {
|
||||||
|
context.showSnackBar('Id is null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (item) {
|
||||||
|
case ContainerMenu.rm:
|
||||||
|
var force = false;
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.attention),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(l10n.askContinue(
|
||||||
|
'${l10n.delete} Container(${dItem.name})',
|
||||||
|
)),
|
||||||
|
UIs.height13,
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
StatefulBuilder(builder: (_, setState) {
|
||||||
|
return Checkbox(
|
||||||
|
value: force,
|
||||||
|
onChanged: (val) => setState(
|
||||||
|
() => force = val ?? false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Text(l10n.force),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
context.pop();
|
||||||
|
context.showLoadingDialog();
|
||||||
|
final result = await _container.delete(id, force);
|
||||||
|
context.pop();
|
||||||
|
if (result != null) {
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.error),
|
||||||
|
child: Text(result.message ?? l10n.unknownError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(l10n.ok),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ContainerMenu.start:
|
||||||
|
context.showLoadingDialog();
|
||||||
|
final result = await _container.start(id);
|
||||||
|
context.pop();
|
||||||
|
if (result != null) {
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.error),
|
||||||
|
child: Text(result.message ?? l10n.unknownError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ContainerMenu.stop:
|
||||||
|
context.showLoadingDialog();
|
||||||
|
final result = await _container.stop(id);
|
||||||
|
context.pop();
|
||||||
|
if (result != null) {
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.error),
|
||||||
|
child: Text(result.message ?? l10n.unknownError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ContainerMenu.restart:
|
||||||
|
context.showLoadingDialog();
|
||||||
|
final result = await _container.restart(id);
|
||||||
|
context.pop();
|
||||||
|
if (result != null) {
|
||||||
|
context.showRoundDialog(
|
||||||
|
title: Text(l10n.error),
|
||||||
|
child: Text(result.message ?? l10n.unknownError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ContainerMenu.logs:
|
||||||
|
AppRoute.ssh(
|
||||||
|
spi: widget.spi,
|
||||||
|
initCmd: 'docker logs -f --tail 100 ${dItem.id}',
|
||||||
|
).go(context);
|
||||||
|
break;
|
||||||
|
case ContainerMenu.terminal:
|
||||||
|
AppRoute.ssh(
|
||||||
|
spi: widget.spi,
|
||||||
|
initCmd: 'docker exec -it ${dItem.id} sh',
|
||||||
|
).go(context);
|
||||||
|
break;
|
||||||
|
// case DockerMenuType.stats:
|
||||||
|
// showRoundDialog(
|
||||||
|
// context: context,
|
||||||
|
// title: Text(l10n.stats),
|
||||||
|
// child: Text(
|
||||||
|
// 'CPU: ${dItem.cpu}\n'
|
||||||
|
// 'Mem: ${dItem.mem}\n'
|
||||||
|
// 'Net: ${dItem.net}\n'
|
||||||
|
// 'Block: ${dItem.disk}',
|
||||||
|
// ),
|
||||||
|
// actions: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () => context.pop(),
|
||||||
|
// child: Text(l10n.ok),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,8 @@ class _ServerDetailOrderPageState extends State<ServerDetailOrderPage> {
|
|||||||
for (final key in keys_) {
|
for (final key in keys_) {
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
}
|
}
|
||||||
final disabled = Defaults.detailCardOrder.where((e) => !keys.contains(e)).toList();
|
final disabled =
|
||||||
|
Defaults.detailCardOrder.where((e) => !keys.contains(e)).toList();
|
||||||
final allKeys = [...keys, ...disabled];
|
final allKeys = [...keys, ...disabled];
|
||||||
return ReorderableListView.builder(
|
return ReorderableListView.builder(
|
||||||
padding: const EdgeInsets.all(7),
|
padding: const EdgeInsets.all(7),
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ class ServerFuncBtnsTopRight extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopupMenu<ServerTabMenuType>(
|
return PopupMenu<ServerTabMenu>(
|
||||||
items: ServerTabMenuType.values
|
items: ServerTabMenu.values
|
||||||
.map((e) => PopupMenuItem<ServerTabMenuType>(
|
.map((e) => PopupMenuItem<ServerTabMenu>(
|
||||||
value: e,
|
value: e,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -93,7 +93,7 @@ class ServerFuncBtns extends StatelessWidget {
|
|||||||
// );
|
// );
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: ServerTabMenuType.values
|
children: ServerTabMenu.values
|
||||||
.map(
|
.map(
|
||||||
(e) => IconButton(
|
(e) => IconButton(
|
||||||
onPressed: () => _onTapMoreBtns(e, spi, context),
|
onPressed: () => _onTapMoreBtns(e, spi, context),
|
||||||
@@ -108,15 +108,15 @@ class ServerFuncBtns extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onTapMoreBtns(
|
void _onTapMoreBtns(
|
||||||
ServerTabMenuType value,
|
ServerTabMenu value,
|
||||||
ServerPrivateInfo spi,
|
ServerPrivateInfo spi,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) async {
|
) async {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case ServerTabMenuType.pkg:
|
case ServerTabMenu.pkg:
|
||||||
_onPkg(context, spi);
|
_onPkg(context, spi);
|
||||||
break;
|
break;
|
||||||
case ServerTabMenuType.sftp:
|
case ServerTabMenu.sftp:
|
||||||
AppRoute.sftp(spi: spi).checkGo(
|
AppRoute.sftp(spi: spi).checkGo(
|
||||||
context: context,
|
context: context,
|
||||||
check: () => _checkClient(context, spi.id),
|
check: () => _checkClient(context, spi.id),
|
||||||
@@ -145,19 +145,19 @@ void _onTapMoreBtns(
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
// break;
|
// break;
|
||||||
case ServerTabMenuType.docker:
|
case ServerTabMenu.container:
|
||||||
AppRoute.docker(spi: spi).checkGo(
|
AppRoute.docker(spi: spi).checkGo(
|
||||||
context: context,
|
context: context,
|
||||||
check: () => _checkClient(context, spi.id),
|
check: () => _checkClient(context, spi.id),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ServerTabMenuType.process:
|
case ServerTabMenu.process:
|
||||||
AppRoute.process(spi: spi).checkGo(
|
AppRoute.process(spi: spi).checkGo(
|
||||||
context: context,
|
context: context,
|
||||||
check: () => _checkClient(context, spi.id),
|
check: () => _checkClient(context, spi.id),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ServerTabMenuType.terminal:
|
case ServerTabMenu.terminal:
|
||||||
_gotoSSH(spi, context);
|
_gotoSSH(spi, context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user