mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
new: container stats (#272)
This commit is contained in:
@@ -1084,6 +1084,18 @@ abstract class S {
|
|||||||
/// **'Different servers will have different logs, and the log is the path to the exit'**
|
/// **'Different servers will have different logs, and the log is the path to the exit'**
|
||||||
String get openLastPathTip;
|
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.
|
/// No description provided for @paste.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -518,6 +518,12 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => 'Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang';
|
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
|
@override
|
||||||
String get paste => 'Einfügen';
|
String get paste => 'Einfügen';
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,12 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => 'Different servers will have different logs, and the log is the path to the exit';
|
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
|
@override
|
||||||
String get paste => 'Paste';
|
String get paste => 'Paste';
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,12 @@ class SFr extends S {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => 'Les serveurs différents auront des journaux différents, et le journal est le chemin de sortie';
|
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
|
@override
|
||||||
String get paste => 'Coller';
|
String get paste => 'Coller';
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,12 @@ class SId extends S {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => 'Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar';
|
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
|
@override
|
||||||
String get paste => 'Tempel';
|
String get paste => 'Tempel';
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,12 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => '不同的服务器会有不同的记录,且记录的是退出时的路径';
|
String get openLastPathTip => '不同的服务器会有不同的记录,且记录的是退出时的路径';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get parseContainerStats => '解析容器占用状态';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get parseContainerStatsTip => 'Docker解析占用状态较为缓慢';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get paste => '粘贴';
|
String get paste => '粘贴';
|
||||||
|
|
||||||
@@ -1390,6 +1396,12 @@ class SZhTw extends SZh {
|
|||||||
@override
|
@override
|
||||||
String get openLastPathTip => '不同的服務器會有不同的記錄,且記錄的是退出時的路徑';
|
String get openLastPathTip => '不同的服務器會有不同的記錄,且記錄的是退出時的路徑';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get parseContainerStats => '解析容器佔用狀態';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get parseContainerStatsTip => 'Docker解析佔用狀態較為緩慢';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get paste => '貼上';
|
String get paste => '貼上';
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ enum ContainerErrType {
|
|||||||
invalidVersion,
|
invalidVersion,
|
||||||
cmdNoPrefix,
|
cmdNoPrefix,
|
||||||
segmentsNotMatch,
|
segmentsNotMatch,
|
||||||
parsePsItem,
|
parsePs,
|
||||||
parseImages,
|
parseImages,
|
||||||
parseStats,
|
parseStats,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:convert';
|
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';
|
import 'package:toolbox/data/model/container/type.dart';
|
||||||
|
|
||||||
abstract final class ContainerPs {
|
abstract final class ContainerPs {
|
||||||
@@ -9,7 +11,14 @@ abstract final class ContainerPs {
|
|||||||
String? get cmd;
|
String? get cmd;
|
||||||
bool get running;
|
bool get running;
|
||||||
|
|
||||||
|
String? cpu;
|
||||||
|
String? mem;
|
||||||
|
String? net;
|
||||||
|
String? disk;
|
||||||
|
|
||||||
factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s);
|
factory ContainerPs.fromRawJson(String s, ContainerType typ) => typ.ps(s);
|
||||||
|
|
||||||
|
void parseStats(String s);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PodmanPs implements ContainerPs {
|
final class PodmanPs implements ContainerPs {
|
||||||
@@ -23,6 +32,11 @@ final class PodmanPs implements ContainerPs {
|
|||||||
final List<String>? names;
|
final List<String>? names;
|
||||||
final int? startedAt;
|
final int? startedAt;
|
||||||
|
|
||||||
|
String? cpu;
|
||||||
|
String? mem;
|
||||||
|
String? net;
|
||||||
|
String? disk;
|
||||||
|
|
||||||
PodmanPs({
|
PodmanPs({
|
||||||
this.command,
|
this.command,
|
||||||
this.created,
|
this.created,
|
||||||
@@ -42,6 +56,23 @@ final class PodmanPs implements ContainerPs {
|
|||||||
@override
|
@override
|
||||||
bool get running => exited != true;
|
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) =>
|
factory PodmanPs.fromRawJson(String str) =>
|
||||||
PodmanPs.fromJson(json.decode(str));
|
PodmanPs.fromJson(json.decode(str));
|
||||||
|
|
||||||
@@ -84,6 +115,11 @@ final class DockerPs implements ContainerPs {
|
|||||||
final String? names;
|
final String? names;
|
||||||
final String? state;
|
final String? state;
|
||||||
|
|
||||||
|
String? cpu;
|
||||||
|
String? mem;
|
||||||
|
String? net;
|
||||||
|
String? disk;
|
||||||
|
|
||||||
DockerPs({
|
DockerPs({
|
||||||
this.command,
|
this.command,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
@@ -102,6 +138,15 @@ final class DockerPs implements ContainerPs {
|
|||||||
@override
|
@override
|
||||||
bool get running => state == 'running';
|
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) =>
|
factory DockerPs.fromRawJson(String str) =>
|
||||||
DockerPs.fromJson(json.decode(str));
|
DockerPs.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
|||||||
@@ -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<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 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<String, dynamic> json) =>
|
|
||||||
ContainerdClient(
|
|
||||||
apiVersion: json["ApiVersion"],
|
|
||||||
version: json["Version"],
|
|
||||||
goVersion: json["GoVersion"],
|
|
||||||
gitCommit: json["GitCommit"],
|
|
||||||
builtTime: json["BuildTime"],
|
|
||||||
os: json["Os"],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
"ApiVersion": apiVersion,
|
|
||||||
"Version": version,
|
|
||||||
"GoVersion": goVersion,
|
|
||||||
"GitCommit": gitCommit,
|
|
||||||
"BuildTime": builtTime,
|
|
||||||
"Os": os,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dartssh2/dartssh2.dart';
|
import 'package:dartssh2/dartssh2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/core/extension/listx.dart';
|
||||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||||
import 'package:toolbox/data/model/app/shell_func.dart';
|
import 'package:toolbox/data/model/app/shell_func.dart';
|
||||||
import 'package:toolbox/data/model/container/image.dart';
|
import 'package:toolbox/data/model/container/image.dart';
|
||||||
import 'package:toolbox/data/model/container/ps.dart';
|
import 'package:toolbox/data/model/container/ps.dart';
|
||||||
import 'package:toolbox/data/model/app/error.dart';
|
import 'package:toolbox/data/model/app/error.dart';
|
||||||
import 'package:toolbox/data/model/container/type.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/logger.dart';
|
||||||
import 'package:toolbox/data/res/store.dart';
|
import 'package:toolbox/data/res/store.dart';
|
||||||
import 'package:toolbox/core/extension/uint8list.dart';
|
import 'package:toolbox/core/extension/uint8list.dart';
|
||||||
@@ -74,14 +75,20 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
final sudo =
|
final sudo =
|
||||||
await _requiresSudo() && Stores.setting.containerTrySudo.fetch();
|
await _requiresSudo() && Stores.setting.containerTrySudo.fetch();
|
||||||
|
final includeStats = Stores.setting.containerParseStat.fetch();
|
||||||
|
|
||||||
await client?.execWithPwd(
|
final code = await client?.execWithPwd(
|
||||||
_wrap(ContainerCmdType.execAll(type, sudo: sudo)),
|
_wrap(ContainerCmdType.execAll(
|
||||||
|
type,
|
||||||
|
sudo: sudo,
|
||||||
|
includeStats: includeStats,
|
||||||
|
)),
|
||||||
context: context,
|
context: context,
|
||||||
onStdout: (data, _) => raw = '$raw$data',
|
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);
|
error = ContainerErr(type: ContainerErrType.notInstalled);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return;
|
return;
|
||||||
@@ -99,12 +106,10 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse docker version
|
// Parse version
|
||||||
final verRaw = ContainerCmdType.version.find(segments);
|
final verRaw = ContainerCmdType.version.find(segments);
|
||||||
debugPrint('version raw = $verRaw\n');
|
|
||||||
try {
|
try {
|
||||||
final containerVersion = Containerd.fromRawJson(verRaw);
|
version = json.decode(verRaw)['Client']['Version'];
|
||||||
version = containerVersion.client.version;
|
|
||||||
} catch (e, trace) {
|
} catch (e, trace) {
|
||||||
error = ContainerErr(
|
error = ContainerErr(
|
||||||
type: ContainerErrType.invalidVersion,
|
type: ContainerErrType.invalidVersion,
|
||||||
@@ -115,14 +120,23 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse docker ps
|
// Parse ps
|
||||||
final psRaw = ContainerCmdType.ps.find(segments);
|
final psRaw = ContainerCmdType.ps.find(segments);
|
||||||
|
try {
|
||||||
final lines = psRaw.split('\n');
|
final lines = psRaw.split('\n');
|
||||||
lines.removeWhere((element) => element.isEmpty);
|
lines.removeWhere((element) => element.isEmpty);
|
||||||
items = lines.map((e) => ContainerPs.fromRawJson(e, type)).toList();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
// Parse docker images
|
// Parse images
|
||||||
final imageRaw = ContainerCmdType.images.find(segments);
|
final imageRaw = ContainerCmdType.images.find(segments);
|
||||||
try {
|
try {
|
||||||
final imgLines = imageRaw.split('\n');
|
final imgLines = imageRaw.split('\n');
|
||||||
@@ -138,29 +152,31 @@ class ContainerProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse docker stats
|
// Parse stats
|
||||||
// final statsRaw = DockerCmdType.stats.find(segments);
|
final statsRaw = ContainerCmdType.stats.find(segments);
|
||||||
// try {
|
try {
|
||||||
// final statsLines = statsRaw.split('\n');
|
final statsLines = statsRaw.split('\n');
|
||||||
// statsLines.removeWhere((element) => element.isEmpty);
|
statsLines.removeWhere((element) => element.isEmpty);
|
||||||
// if (statsLines.isNotEmpty) statsLines.removeAt(0);
|
for (var item in items!) {
|
||||||
// for (var item in items!) {
|
final id = item.id;
|
||||||
// final statsLine = statsLines.firstWhere(
|
if (id == null) continue;
|
||||||
// (element) => element.contains(item.containerId),
|
final statsLine = statsLines.firstWhereOrNull(
|
||||||
// orElse: () => '',
|
/// Use 5 characters to match the container id, possibility of mismatch
|
||||||
// );
|
/// is very low.
|
||||||
// if (statsLine.isEmpty) continue;
|
(element) => element.contains(id.substring(0, 5)),
|
||||||
// item.parseStats(statsLine);
|
);
|
||||||
// }
|
if (statsLine == null) continue;
|
||||||
// } catch (e, trace) {
|
item.parseStats(statsLine);
|
||||||
// error = DockerErr(
|
}
|
||||||
// type: DockerErrType.parseStats,
|
} catch (e, trace) {
|
||||||
// message: '$e',
|
error = ContainerErr(
|
||||||
// );
|
type: ContainerErrType.parseStats,
|
||||||
// _logger.warning('Parse docker stats: $statsRaw', e, trace);
|
message: '$e',
|
||||||
// } finally {
|
);
|
||||||
// notifyListeners();
|
Loggers.parse.warning('Parse docker stats: $statsRaw', e, trace);
|
||||||
// }
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ContainerErr?> stop(String id) async => await run('stop $id');
|
Future<ContainerErr?> stop(String id) async => await run('stop $id');
|
||||||
@@ -223,21 +239,32 @@ const _jsonFmt = '--format "{{json .}}"';
|
|||||||
enum ContainerCmdType {
|
enum ContainerCmdType {
|
||||||
version,
|
version,
|
||||||
ps,
|
ps,
|
||||||
//stats,
|
stats,
|
||||||
images,
|
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;
|
final prefix = sudo ? 'sudo -S ${type.name}' : type.name;
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
ContainerCmdType.version => '$prefix version $_jsonFmt',
|
||||||
ContainerCmdType.ps => '$prefix ps -a $_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',
|
ContainerCmdType.images => '$prefix image ls $_jsonFmt',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static String execAll(ContainerType type, {bool sudo = false}) => values
|
static String execAll(
|
||||||
|
ContainerType type, {
|
||||||
|
bool sudo = false,
|
||||||
|
bool includeStats = false,
|
||||||
|
}) {
|
||||||
|
return ContainerCmdType.values
|
||||||
.map((e) => e.exec(type, sudo: sudo))
|
.map((e) => e.exec(type, sudo: sudo))
|
||||||
.join(' && echo $seperator && ');
|
.join(' && echo $seperator && ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ class SettingStore extends PersistentStore {
|
|||||||
/// Keep previous server status when err occurs
|
/// Keep previous server status when err occurs
|
||||||
late final keepStatusWhenErr = property('keepStatusWhenErr', false);
|
late final keepStatusWhenErr = property('keepStatusWhenErr', false);
|
||||||
|
|
||||||
|
/// Parse container stat
|
||||||
|
late final containerParseStat = property('containerParseStat', true);
|
||||||
|
|
||||||
// Never show these settings for users
|
// Never show these settings for users
|
||||||
//
|
//
|
||||||
// ------BEGIN------
|
// ------BEGIN------
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "Öffnen",
|
"open": "Öffnen",
|
||||||
"openLastPath": "Öffnen Sie den letzten Pfad",
|
"openLastPath": "Öffnen Sie den letzten Pfad",
|
||||||
"openLastPathTip": "Verschiedene Server haben unterschiedliche Einträge, und der Eintrag ist der Pfad zum Ausgang",
|
"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",
|
"paste": "Einfügen",
|
||||||
"path": "Pfad",
|
"path": "Pfad",
|
||||||
"percentOfSize": "{percent}% von {size}",
|
"percentOfSize": "{percent}% von {size}",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "Open",
|
"open": "Open",
|
||||||
"openLastPath": "Open the last path",
|
"openLastPath": "Open the last path",
|
||||||
"openLastPathTip": "Different servers will have different logs, and the log is the path to the exit",
|
"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",
|
"paste": "Paste",
|
||||||
"path": "Path",
|
"path": "Path",
|
||||||
"percentOfSize": "{percent}% of {size}",
|
"percentOfSize": "{percent}% of {size}",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "Ouvrir",
|
"open": "Ouvrir",
|
||||||
"openLastPath": "Ouvrir le dernier chemin",
|
"openLastPath": "Ouvrir le dernier chemin",
|
||||||
"openLastPathTip": "Les serveurs différents auront des journaux différents, et le journal est le chemin de sortie",
|
"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",
|
"paste": "Coller",
|
||||||
"path": "Chemin",
|
"path": "Chemin",
|
||||||
"percentOfSize": "{percent}% de {size}",
|
"percentOfSize": "{percent}% de {size}",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "Membuka",
|
"open": "Membuka",
|
||||||
"openLastPath": "Buka jalur terakhir",
|
"openLastPath": "Buka jalur terakhir",
|
||||||
"openLastPathTip": "Server yang berbeda akan memiliki catatan yang berbeda, dan catatan tersebut adalah jalur menuju pintu keluar",
|
"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",
|
"paste": "Tempel",
|
||||||
"path": "Jalur",
|
"path": "Jalur",
|
||||||
"percentOfSize": "{percent}% dari {size}",
|
"percentOfSize": "{percent}% dari {size}",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "打开",
|
"open": "打开",
|
||||||
"openLastPath": "打开上次的路径",
|
"openLastPath": "打开上次的路径",
|
||||||
"openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径",
|
"openLastPathTip": "不同的服务器会有不同的记录,且记录的是退出时的路径",
|
||||||
|
"parseContainerStats": "解析容器占用状态",
|
||||||
|
"parseContainerStatsTip": "Docker解析占用状态较为缓慢",
|
||||||
"paste": "粘贴",
|
"paste": "粘贴",
|
||||||
"path": "路径",
|
"path": "路径",
|
||||||
"percentOfSize": "{size} 的 {percent}%",
|
"percentOfSize": "{size} 的 {percent}%",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@
|
|||||||
"open": "打開",
|
"open": "打開",
|
||||||
"openLastPath": "打開上次的路徑",
|
"openLastPath": "打開上次的路徑",
|
||||||
"openLastPathTip": "不同的服務器會有不同的記錄,且記錄的是退出時的路徑",
|
"openLastPathTip": "不同的服務器會有不同的記錄,且記錄的是退出時的路徑",
|
||||||
|
"parseContainerStats": "解析容器佔用狀態",
|
||||||
|
"parseContainerStatsTip": "Docker解析佔用狀態較為緩慢",
|
||||||
"paste": "貼上",
|
"paste": "貼上",
|
||||||
"path": "路徑",
|
"path": "路徑",
|
||||||
"percentOfSize": "{size} 的 {percent}%",
|
"percentOfSize": "{size} 的 {percent}%",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
hostId: widget.spi.id,
|
hostId: widget.spi.id,
|
||||||
context: context,
|
context: context,
|
||||||
);
|
);
|
||||||
|
late Size _size;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -50,6 +51,12 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_size = MediaQuery.of(context).size;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
@@ -113,22 +120,22 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
return UIs.centerLoading;
|
return UIs.centerLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
final items = <Widget>[
|
return ListView(
|
||||||
|
padding: const EdgeInsets.only(left: 13, right: 13, top: 13, bottom: 37),
|
||||||
|
children: <Widget>[
|
||||||
_buildLoading(),
|
_buildLoading(),
|
||||||
_buildVersion(),
|
_buildVersion(),
|
||||||
_buildPs(),
|
_buildPs(),
|
||||||
_buildImage(),
|
_buildImage(),
|
||||||
_buildEditHost(),
|
_buildEditHost(),
|
||||||
_buildSwitchProvider(),
|
_buildSwitchProvider(),
|
||||||
].map((e) => CardX(child: e)).toList();
|
],
|
||||||
return ListView(
|
|
||||||
padding: const EdgeInsets.only(left: 13, right: 13, top: 13, bottom: 37),
|
|
||||||
children: items,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImage() {
|
Widget _buildImage() {
|
||||||
return ExpandTile(
|
return CardX(
|
||||||
|
child: ExpandTile(
|
||||||
title: Text(l10n.imagesList),
|
title: Text(l10n.imagesList),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
l10n.dockerImagesFmt(_container.images!.length),
|
l10n.dockerImagesFmt(_container.images!.length),
|
||||||
@@ -136,7 +143,7 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
),
|
),
|
||||||
initiallyExpanded: (_container.images?.length ?? 0) <= 3,
|
initiallyExpanded: (_container.images?.length ?? 0) <= 3,
|
||||||
children: _container.images?.map(_buildImageItem).toList() ?? [],
|
children: _container.images?.map(_buildImageItem).toList() ?? [],
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImageItem(ContainerImg e) {
|
Widget _buildImageItem(ContainerImg e) {
|
||||||
@@ -169,7 +176,8 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildVersion() {
|
Widget _buildVersion() {
|
||||||
return Padding(
|
return CardX(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -178,31 +186,82 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
Text(_container.version ?? l10n.unknown),
|
Text(_container.version ?? l10n.unknown),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPs() {
|
Widget _buildPs() {
|
||||||
final items = _container.items;
|
final items = _container.items;
|
||||||
if (items == null) return UIs.placeholder;
|
if (items == null) return UIs.placeholder;
|
||||||
return ExpandTile(
|
return Column(
|
||||||
title: Text(l10n.containerStatus),
|
|
||||||
subtitle: Text(
|
|
||||||
_buildPsCardSubtitle(items),
|
|
||||||
style: UIs.textGrey,
|
|
||||||
),
|
|
||||||
initiallyExpanded: items.length <= 7,
|
|
||||||
children: items.map(_buildPsItem).toList(),
|
children: items.map(_buildPsItem).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPsItem(ContainerPs item) {
|
Widget _buildPsItem(ContainerPs item) {
|
||||||
return ListTile(
|
return CardX(
|
||||||
title: Text(item.name ?? l10n.unknown),
|
child: Padding(
|
||||||
subtitle: Text(
|
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}',
|
'${item.image ?? l10n.unknown} - ${item.running ? l10n.running : l10n.stopped}',
|
||||||
style: UIs.text13Grey,
|
style: UIs.text13Grey,
|
||||||
),
|
),
|
||||||
trailing: _buildMoreBtn(item),
|
_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),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,14 +272,14 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _buildPsCardSubtitle(List<ContainerPs> running) {
|
// String _buildPsCardSubtitle(List<ContainerPs> running) {
|
||||||
final runningCount = running.where((element) => element.running).length;
|
// final runningCount = running.where((element) => element.running).length;
|
||||||
final stoped = running.length - runningCount;
|
// final stoped = running.length - runningCount;
|
||||||
if (stoped == 0) {
|
// if (stoped == 0) {
|
||||||
return l10n.dockerStatusRunningFmt(runningCount);
|
// return l10n.dockerStatusRunningFmt(runningCount);
|
||||||
}
|
// }
|
||||||
return l10n.dockerStatusRunningAndStoppedFmt(runningCount, stoped);
|
// return l10n.dockerStatusRunningAndStoppedFmt(runningCount, stoped);
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget _buildEditHost() {
|
Widget _buildEditHost() {
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
@@ -241,9 +300,9 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
child: Text(l10n.dockerEditHost),
|
child: Text(l10n.dockerEditHost),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Column(
|
return CardX(child: Column(
|
||||||
children: children,
|
children: children,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwitchProvider() {
|
Widget _buildSwitchProvider() {
|
||||||
@@ -263,7 +322,7 @@ class _ContainerPageState extends State<ContainerPage> {
|
|||||||
child: Text(l10n.switchTo('Podman')),
|
child: Text(l10n.switchTo('Podman')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return child;
|
return CardX(child: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showAddFAB() async {
|
Future<void> _showAddFAB() async {
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
children: [
|
children: [
|
||||||
_buildUsePodman(),
|
_buildUsePodman(),
|
||||||
_buildContainerTrySudo(),
|
_buildContainerTrySudo(),
|
||||||
|
_buildContainerParseStat(),
|
||||||
].map((e) => CardX(child: e)).toList(),
|
].map((e) => CardX(child: e)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1166,4 +1167,12 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
trailing: StoreSwitch(prop: _setting.keepStatusWhenErr),
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user