diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 0820811e..d5ae979c 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -1084,6 +1084,18 @@ abstract class S { /// **'Different servers will have different logs, and the log is the path to the exit'** String get openLastPathTip; + /// No description provided for @parseContainerStats. + /// + /// In en, this message translates to: + /// **'Parse the container occupancy status'** + String get parseContainerStats; + + /// No description provided for @parseContainerStatsTip. + /// + /// In en, this message translates to: + /// **'Docker parsing the occupancy status is relatively slow.'** + String get parseContainerStatsTip; + /// No description provided for @paste. /// /// In en, this message translates to: diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index 9de9a06d..b6b5e4c2 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -518,6 +518,12 @@ class SDe extends S { @override String get openLastPathTip => 'Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang'; + @override + String get parseContainerStats => 'Den Status der Container-Belegung analysieren'; + + @override + String get parseContainerStatsTip => 'Das Analysieren des Belegungsstatus durch Docker ist relativ langsam'; + @override String get paste => 'Einfügen'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 6ab31f4a..5a26dd1b 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -518,6 +518,12 @@ class SEn extends S { @override String get openLastPathTip => 'Different servers will have different logs, and the log is the path to the exit'; + @override + String get parseContainerStats => 'Parse the container occupancy status'; + + @override + String get parseContainerStatsTip => 'Docker parsing the occupancy status is relatively slow.'; + @override String get paste => 'Paste'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_fr.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_fr.dart index ddd759cb..060f23da 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_fr.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_fr.dart @@ -518,6 +518,12 @@ class SFr extends S { @override String get openLastPathTip => 'Les serveurs différents auront des journaux différents, et le journal est le chemin de sortie'; + @override + String get parseContainerStats => 'Analyser l\'état d\'occupation du conteneur'; + + @override + String get parseContainerStatsTip => 'L\'analyse de l\'état d\'occupation par Docker est relativement lente.'; + @override String get paste => 'Coller'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart index 1f65b923..91831270 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart @@ -518,6 +518,12 @@ class SId extends S { @override String get openLastPathTip => 'Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar'; + @override + String get parseContainerStats => 'Memecahkan status okupansi kontainer'; + + @override + String get parseContainerStatsTip => 'Parsing status okupansi oleh Docker agak lambat'; + @override String get paste => 'Tempel'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index 410251c3..dfac9c3d 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -518,6 +518,12 @@ class SZh extends S { @override String get openLastPathTip => '不同的服务器会有不同的记录,且记录的是退出时的路径'; + @override + String get parseContainerStats => '解析容器占用状态'; + + @override + String get parseContainerStatsTip => 'Docker解析占用状态较为缓慢'; + @override String get paste => '粘贴'; @@ -1390,6 +1396,12 @@ class SZhTw extends SZh { @override String get openLastPathTip => '不同的服務器會有不同的記錄,且記錄的是退出時的路徑'; + @override + String get parseContainerStats => '解析容器佔用狀態'; + + @override + String get parseContainerStatsTip => 'Docker解析佔用狀態較為緩慢'; + @override String get paste => '貼上'; diff --git a/lib/data/model/app/error.dart b/lib/data/model/app/error.dart index 842acee3..1b139ccb 100644 --- a/lib/data/model/app/error.dart +++ b/lib/data/model/app/error.dart @@ -40,7 +40,7 @@ enum ContainerErrType { invalidVersion, cmdNoPrefix, segmentsNotMatch, - parsePsItem, + parsePs, parseImages, parseStats, } diff --git a/lib/data/model/container/ps.dart b/lib/data/model/container/ps.dart index 5ba085e9..e0ce2a42 100644 --- a/lib/data/model/container/ps.dart +++ b/lib/data/model/container/ps.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:toolbox/core/extension/context/locale.dart'; +import 'package:toolbox/core/extension/numx.dart'; import 'package:toolbox/data/model/container/type.dart'; abstract final class ContainerPs { @@ -9,7 +11,14 @@ abstract final class ContainerPs { String? get cmd; bool get running; + String? cpu; + String? mem; + String? net; + String? disk; + factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s); + + void parseStats(String s); } final class PodmanPs implements ContainerPs { @@ -23,6 +32,11 @@ final class PodmanPs implements ContainerPs { final List? names; final int? startedAt; + String? cpu; + String? mem; + String? net; + String? disk; + PodmanPs({ this.command, this.created, @@ -42,6 +56,23 @@ final class PodmanPs implements ContainerPs { @override bool get running => exited != true; + @override + void parseStats(String s) { + final stats = json.decode(s); + final cpuD = (stats['CPU'] as double? ?? 0).toStringAsFixed(1); + final cpuAvgD = (stats['AvgCPU'] as double? ?? 0).toStringAsFixed(1); + cpu = '$cpuD% / ${l10n.pingAvg} $cpuAvgD%'; + final memLimit = (stats['MemLimit'] as int? ?? 0).bytes2Str; + final memUsage = (stats['MemUsage'] as int? ?? 0).bytes2Str; + mem = '$memUsage / $memLimit'; + final netIn = (stats['NetInput'] as int? ?? 0).bytes2Str; + final netOut = (stats['NetOutput'] as int? ?? 0).bytes2Str; + net = '↓ $netIn / ↑ $netOut'; + final diskIn = (stats['BlockInput'] as int? ?? 0).bytes2Str; + final diskOut = (stats['BlockOutput'] as int? ?? 0).bytes2Str; + disk = '${l10n.read} $diskOut / ${l10n.write} $diskIn'; + } + factory PodmanPs.fromRawJson(String str) => PodmanPs.fromJson(json.decode(str)); @@ -84,6 +115,11 @@ final class DockerPs implements ContainerPs { final String? names; final String? state; + String? cpu; + String? mem; + String? net; + String? disk; + DockerPs({ this.command, this.createdAt, @@ -102,6 +138,15 @@ final class DockerPs implements ContainerPs { @override bool get running => state == 'running'; + @override + void parseStats(String s) { + final stats = json.decode(s); + cpu = stats['CPUPerc']; + mem = stats['MemUsage']; + net = stats['NetIO']; + disk = stats['BlockIO']; + } + factory DockerPs.fromRawJson(String str) => DockerPs.fromJson(json.decode(str)); diff --git a/lib/data/model/container/version.dart b/lib/data/model/container/version.dart deleted file mode 100644 index 2e021a71..00000000 --- a/lib/data/model/container/version.dart +++ /dev/null @@ -1,64 +0,0 @@ -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 json) => Containerd( - client: ContainerdClient.fromJson(json["Client"]), - ); - - Map toJson() => { - "Client": client.toJson(), - }; -} - -class ContainerdClient { - final String apiVersion; - final String version; - final String goVersion; - final String gitCommit; - final String builtTime; - final String os; - - ContainerdClient({ - required this.apiVersion, - required this.version, - required this.goVersion, - required this.gitCommit, - required this.builtTime, - required this.os, - }); - - factory ContainerdClient.fromRawJson(String str) => - ContainerdClient.fromJson(json.decode(str)); - - String toRawJson() => json.encode(toJson()); - - factory ContainerdClient.fromJson(Map json) => - ContainerdClient( - apiVersion: json["ApiVersion"], - version: json["Version"], - goVersion: json["GoVersion"], - gitCommit: json["GitCommit"], - builtTime: json["BuildTime"], - os: json["Os"], - ); - - Map toJson() => { - "ApiVersion": apiVersion, - "Version": version, - "GoVersion": goVersion, - "GitCommit": gitCommit, - "BuildTime": builtTime, - "Os": os, - }; -} diff --git a/lib/data/provider/container.dart b/lib/data/provider/container.dart index 19ecb4e0..53ffd011 100644 --- a/lib/data/provider/container.dart +++ b/lib/data/provider/container.dart @@ -1,14 +1,15 @@ import 'dart:async'; +import 'dart:convert'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; +import 'package:toolbox/core/extension/listx.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'; import 'package:toolbox/core/extension/uint8list.dart'; @@ -74,14 +75,20 @@ class ContainerProvider extends ChangeNotifier { final sudo = await _requiresSudo() && Stores.setting.containerTrySudo.fetch(); + final includeStats = Stores.setting.containerParseStat.fetch(); - await client?.execWithPwd( - _wrap(ContainerCmdType.execAll(type, sudo: sudo)), + final code = await client?.execWithPwd( + _wrap(ContainerCmdType.execAll( + type, + sudo: sudo, + includeStats: includeStats, + )), context: context, onStdout: (data, _) => raw = '$raw$data', ); - if (raw.contains(_dockerNotFound)) { + /// Code 127 means command not found + if (code == 127 || raw.contains(_dockerNotFound)) { error = ContainerErr(type: ContainerErrType.notInstalled); notifyListeners(); return; @@ -99,12 +106,10 @@ class ContainerProvider extends ChangeNotifier { return; } - // Parse docker version + // Parse version final verRaw = ContainerCmdType.version.find(segments); - debugPrint('version raw = $verRaw\n'); try { - final containerVersion = Containerd.fromRawJson(verRaw); - version = containerVersion.client.version; + version = json.decode(verRaw)['Client']['Version']; } catch (e, trace) { error = ContainerErr( type: ContainerErrType.invalidVersion, @@ -115,14 +120,23 @@ class ContainerProvider extends ChangeNotifier { notifyListeners(); } - // Parse docker ps + // Parse ps final psRaw = ContainerCmdType.ps.find(segments); + try { + final lines = psRaw.split('\n'); + lines.removeWhere((element) => element.isEmpty); + items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList(); + } catch (e, trace) { + error = ContainerErr( + type: ContainerErrType.parsePs, + message: '$e', + ); + Loggers.parse.warning('Container ps failed', e, trace); + } finally { + notifyListeners(); + } - final lines = psRaw.split('\n'); - lines.removeWhere((element) => element.isEmpty); - items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList(); - - // Parse docker images + // Parse images final imageRaw = ContainerCmdType.images.find(segments); try { final imgLines = imageRaw.split('\n'); @@ -138,29 +152,31 @@ class ContainerProvider extends ChangeNotifier { 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(); - // } + // Parse stats + final statsRaw = ContainerCmdType.stats.find(segments); + try { + final statsLines = statsRaw.split('\n'); + statsLines.removeWhere((element) => element.isEmpty); + for (var item in items!) { + final id = item.id; + if (id == null) continue; + final statsLine = statsLines.firstWhereOrNull( + /// Use 5 characters to match the container id, possibility of mismatch + /// is very low. + (element) => element.contains(id.substring(0, 5)), + ); + if (statsLine == null) continue; + item.parseStats(statsLine); + } + } catch (e, trace) { + error = ContainerErr( + type: ContainerErrType.parseStats, + message: '$e', + ); + Loggers.parse.warning('Parse docker stats: $statsRaw', e, trace); + } finally { + notifyListeners(); + } } Future stop(String id) async => await run('stop $id'); @@ -223,21 +239,32 @@ const _jsonFmt = '--format "{{json .}}"'; enum ContainerCmdType { version, ps, - //stats, + stats, images, ; - String exec(ContainerType type, {bool sudo = false}) { + String exec( + ContainerType type, { + bool sudo = false, + bool includeStats = false, + }) { final prefix = sudo ? 'sudo -S ${type.name}' : type.name; return switch (this) { ContainerCmdType.version => '$prefix version $_jsonFmt', ContainerCmdType.ps => '$prefix ps -a $_jsonFmt', - // DockerCmdType.stats => '$prefix stats --no-stream'; + ContainerCmdType.stats => + includeStats ? '$prefix stats --no-stream $_jsonFmt' : 'echo PASS', ContainerCmdType.images => '$prefix image ls $_jsonFmt', }; } - static String execAll(ContainerType type, {bool sudo = false}) => values - .map((e) => e.exec(type, sudo: sudo)) - .join(' && echo $seperator && '); + static String execAll( + ContainerType type, { + bool sudo = false, + bool includeStats = false, + }) { + return ContainerCmdType.values + .map((e) => e.exec(type, sudo: sudo)) + .join(' && echo $seperator && '); + } } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 0eda6f37..776cf113 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -233,6 +233,9 @@ class SettingStore extends PersistentStore { /// Keep previous server status when err occurs late final keepStatusWhenErr = property('keepStatusWhenErr', false); + /// Parse container stat + late final containerParseStat = property('containerParseStat', true); + // Never show these settings for users // // ------BEGIN------ diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 462b138a..12b44260 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -164,6 +164,8 @@ "open": "Öffnen", "openLastPath": "Öffnen Sie den letzten Pfad", "openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang", + "parseContainerStats": "Den Status der Container-Belegung analysieren", + "parseContainerStatsTip": "Das Analysieren des Belegungsstatus durch Docker ist relativ langsam", "paste": "Einfügen", "path": "Pfad", "percentOfSize": "{percent}% von {size}", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c744a9b1..b1445fd7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -164,6 +164,8 @@ "open": "Open", "openLastPath": "Open the last path", "openLastPathTip": "Different servers will have different logs, and the log is the path to the exit", + "parseContainerStats": "Parse the container occupancy status", + "parseContainerStatsTip": "Docker parsing the occupancy status is relatively slow.", "paste": "Paste", "path": "Path", "percentOfSize": "{percent}% of {size}", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1d40e7e5..b7bd72ba 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -164,6 +164,8 @@ "open": "Ouvrir", "openLastPath": "Ouvrir le dernier chemin", "openLastPathTip": "Les serveurs différents auront des journaux différents, et le journal est le chemin de sortie", + "parseContainerStats": "Analyser l'état d'occupation du conteneur", + "parseContainerStatsTip": "L'analyse de l'état d'occupation par Docker est relativement lente.", "paste": "Coller", "path": "Chemin", "percentOfSize": "{percent}% de {size}", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 6a3dba8c..f2aecb5d 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -164,6 +164,8 @@ "open": "Membuka", "openLastPath": "Buka jalur terakhir", "openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar", + "parseContainerStats": "Memecahkan status okupansi kontainer", + "parseContainerStatsTip": "Parsing status okupansi oleh Docker agak lambat", "paste": "Tempel", "path": "Jalur", "percentOfSize": "{percent}% dari {size}", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 365d83db..c7fd4c7b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -164,6 +164,8 @@ "open": "打开", "openLastPath": "打开上次的路径", "openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径", + "parseContainerStats": "解析容器占用状态", + "parseContainerStatsTip": "Docker解析占用状态较为缓慢", "paste": "粘贴", "path": "路径", "percentOfSize": "{size} 的 {percent}%", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index 3e1df48f..c762039d 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -164,6 +164,8 @@ "open": "打開", "openLastPath": "打開上次的路徑", "openLastPathTip": "不同的服務器會有不同的記錄,且記錄的是退出時的路徑", + "parseContainerStats": "解析容器佔用狀態", + "parseContainerStatsTip": "Docker解析佔用狀態較為緩慢", "paste": "貼上", "path": "路徑", "percentOfSize": "{size} 的 {percent}%", diff --git a/lib/view/page/container.dart b/lib/view/page/container.dart index 1dc085b0..e8b1ed15 100644 --- a/lib/view/page/container.dart +++ b/lib/view/page/container.dart @@ -38,6 +38,7 @@ class _ContainerPageState extends State { hostId: widget.spi.id, context: context, ); + late Size _size; @override void dispose() { @@ -50,6 +51,12 @@ class _ContainerPageState extends State { super.initState(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _size = MediaQuery.of(context).size; + } + @override Widget build(BuildContext context) { return ChangeNotifierProvider( @@ -113,22 +120,22 @@ class _ContainerPageState extends State { return UIs.centerLoading; } - final items = [ - _buildLoading(), - _buildVersion(), - _buildPs(), - _buildImage(), - _buildEditHost(), - _buildSwitchProvider(), - ].map((e) => CardX(child: e)).toList(); return ListView( padding: const EdgeInsets.only(left: 13, right: 13, top: 13, bottom: 37), - children: items, + children: [ + _buildLoading(), + _buildVersion(), + _buildPs(), + _buildImage(), + _buildEditHost(), + _buildSwitchProvider(), + ], ); } Widget _buildImage() { - return ExpandTile( + return CardX( + child: ExpandTile( title: Text(l10n.imagesList), subtitle: Text( l10n.dockerImagesFmt(_container.images!.length), @@ -136,7 +143,7 @@ class _ContainerPageState extends State { ), initiallyExpanded: (_container.images?.length ?? 0) <= 3, children: _container.images?.map(_buildImageItem).toList() ?? [], - ); + )); } Widget _buildImageItem(ContainerImg e) { @@ -169,7 +176,8 @@ class _ContainerPageState extends State { } Widget _buildVersion() { - return Padding( + return CardX( + child: Padding( padding: const EdgeInsets.all(17), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -178,31 +186,82 @@ class _ContainerPageState extends State { 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, + return Column( children: items.map(_buildPsItem).toList(), ); } Widget _buildPsItem(ContainerPs item) { - return ListTile( - title: Text(item.name ?? l10n.unknown), - subtitle: Text( - '${item.image ?? l10n.unknown} - ${item.running ? l10n.running : l10n.stopped}', - style: UIs.text13Grey, + return CardX( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 11), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(item.name ?? l10n.unknown, style: UIs.text15), + const SizedBox(height: 3), + _buildMoreBtn(item), + ], + ), + Text( + '${item.image ?? l10n.unknown} - ${item.running ? l10n.running : l10n.stopped}', + style: UIs.text13Grey, + ), + _buildPsItemStats(item), + ], + ), + ), + ); + } + + Widget _buildPsItemStats(ContainerPs item) { + if (item.cpu == null || item.mem == null) return UIs.placeholder; + return Column( + children: [ + UIs.height13, + Row( + children: [ + _buildPsItemStatsItem('CPU', item.cpu, Icons.memory), + UIs.width13, + _buildPsItemStatsItem('Net', item.net, Icons.network_cell), + ], + ), + Row( + children: [ + _buildPsItemStatsItem( + 'Mem', item.mem, Icons.settings_input_component), + UIs.width13, + _buildPsItemStatsItem('Disk', item.disk, Icons.storage), + ], + ), + ], + ); + } + + Widget _buildPsItemStatsItem(String title, String? value, IconData icon) { + return SizedBox( + width: _size.width / 2 - 41, + child: Column( + children: [ + Row( + children: [ + Icon(icon, size: 12, color: Colors.grey), + UIs.width7, + Text(value ?? l10n.unknown, style: UIs.text11Grey), + ], + ) + ], ), - trailing: _buildMoreBtn(item), ); } @@ -213,14 +272,14 @@ class _ContainerPageState extends State { ); } - String _buildPsCardSubtitle(List 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); - } + // String _buildPsCardSubtitle(List 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 = []; @@ -241,9 +300,9 @@ class _ContainerPageState extends State { child: Text(l10n.dockerEditHost), ), ); - return Column( + return CardX(child: Column( children: children, - ); + )); } Widget _buildSwitchProvider() { @@ -263,7 +322,7 @@ class _ContainerPageState extends State { child: Text(l10n.switchTo('Podman')), ); } - return child; + return CardX(child: child); } Future _showAddFAB() async { diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 60d7ece1..249d8eee 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -222,6 +222,7 @@ class _SettingPageState extends State { children: [ _buildUsePodman(), _buildContainerTrySudo(), + _buildContainerParseStat(), ].map((e) => CardX(child: e)).toList(), ); } @@ -1166,4 +1167,12 @@ class _SettingPageState extends State { trailing: StoreSwitch(prop: _setting.keepStatusWhenErr), ); } + + Widget _buildContainerParseStat() { + return ListTile( + title: Text(l10n.parseContainerStats), + subtitle: Text(l10n.parseContainerStatsTip, style: UIs.textGrey), + trailing: StoreSwitch(prop: _setting.containerParseStat), + ); + } }