mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
opt. & new
- opt.: docker page perf - new: docker stats
This commit is contained in:
@@ -36,7 +36,8 @@ enum DockerErrType {
|
||||
noClient,
|
||||
notInstalled,
|
||||
invalidVersion,
|
||||
cmdNoPrefix
|
||||
cmdNoPrefix,
|
||||
segmentsNotMatch,
|
||||
}
|
||||
|
||||
class DockerErr extends Err<DockerErrType> {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import '../../res/server_cmd.dart';
|
||||
|
||||
class AppShellFunc {
|
||||
final String name;
|
||||
final String cmd;
|
||||
final String flag;
|
||||
|
||||
const AppShellFunc(this.name, this.cmd, this.flag);
|
||||
|
||||
String get exec => 'sh $shellPath -$flag';
|
||||
}
|
||||
|
||||
typedef AppShellFuncs = List<AppShellFunc>;
|
||||
@@ -39,3 +43,8 @@ esac
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// enum AppShellFuncType {
|
||||
// status,
|
||||
// docker;
|
||||
// }
|
||||
|
||||
@@ -8,6 +8,10 @@ class DockerPsItem {
|
||||
late String status;
|
||||
late String ports;
|
||||
late String name;
|
||||
String? cpu;
|
||||
String? mem;
|
||||
String? net;
|
||||
String? disk;
|
||||
|
||||
DockerPsItem(
|
||||
this.containerId,
|
||||
@@ -37,6 +41,20 @@ class DockerPsItem {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:toolbox/core/extension/ssh_client.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/extension/uint8list.dart';
|
||||
import 'package:toolbox/core/provider_base.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/server_cmd.dart';
|
||||
import 'package:toolbox/data/store/docker.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
|
||||
@@ -18,9 +18,6 @@ final _versionReg = RegExp(r'(Version:)\s+([0-9]+\.[0-9]+\.[0-9]+)');
|
||||
final _editionReg = RegExp(r'(Client:)\s+(.+-.+)');
|
||||
final _dockerPrefixReg = RegExp(r'(sudo )?docker ');
|
||||
|
||||
const _dockerPS = 'docker ps -a';
|
||||
const _dockerImgs = 'docker images';
|
||||
|
||||
final _logger = Logger('DOCKER');
|
||||
|
||||
class DockerProvider extends BusyProvider {
|
||||
@@ -54,49 +51,59 @@ class DockerProvider extends BusyProvider {
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (isBusy) return;
|
||||
final verRaw = await client!.run('docker version'.withLangExport).string;
|
||||
if (verRaw.contains(_dockerNotFound)) {
|
||||
setBusyState();
|
||||
|
||||
var raw = '';
|
||||
await client!.exec(
|
||||
shellFuncDocker.exec,
|
||||
onStderr: _onPwd,
|
||||
onStdout: (data, _) => raw = '$raw$data',
|
||||
);
|
||||
|
||||
if (raw.contains(_dockerNotFound)) {
|
||||
error = DockerErr(type: DockerErrType.notInstalled);
|
||||
setBusyState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check result segments count
|
||||
final segments = raw.split(seperator);
|
||||
if (segments.length != dockerCmds.length) {
|
||||
error = DockerErr(type: DockerErrType.segmentsNotMatch);
|
||||
setBusyState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse docker version
|
||||
final verRaw = segments[0];
|
||||
try {
|
||||
version = _versionReg.firstMatch(verRaw)?.group(2);
|
||||
edition = _editionReg.firstMatch(verRaw)?.group(2);
|
||||
} catch (e) {
|
||||
error = DockerErr(type: DockerErrType.unknown, message: e.toString());
|
||||
rethrow;
|
||||
}
|
||||
|
||||
// Parse docker ps
|
||||
final psRaw = segments[1];
|
||||
try {
|
||||
setBusyState();
|
||||
final cmd = _wrap(_dockerPS);
|
||||
|
||||
// run docker ps
|
||||
var raw = '';
|
||||
await client!.exec(
|
||||
cmd,
|
||||
onStderr: _onPwd,
|
||||
onStdout: (data, _) => raw = '$raw$data',
|
||||
);
|
||||
|
||||
// parse result
|
||||
final lines = raw.split('\n');
|
||||
lines.removeAt(0);
|
||||
final lines = psRaw.split('\n');
|
||||
lines.removeWhere((element) => element.isEmpty);
|
||||
lines.removeAt(0);
|
||||
items = lines.map((e) => DockerPsItem.fromRawString(e)).toList();
|
||||
} catch (e) {
|
||||
error = DockerErr(type: DockerErrType.unknown, message: e.toString());
|
||||
rethrow;
|
||||
} finally {
|
||||
setBusyState(false);
|
||||
}
|
||||
|
||||
final imageCmd = _wrap(_dockerImgs);
|
||||
raw = '';
|
||||
await client!.exec(
|
||||
imageCmd,
|
||||
onStderr: _onPwd,
|
||||
onStdout: (data, _) => raw = '$raw$data',
|
||||
);
|
||||
|
||||
final imageLines = raw.split('\n');
|
||||
imageLines.removeAt(0);
|
||||
// Parse docker images
|
||||
final imageRaw = segments[3];
|
||||
try {
|
||||
final imageLines = imageRaw.split('\n');
|
||||
imageLines.removeWhere((element) => element.isEmpty);
|
||||
imageLines.removeAt(0);
|
||||
images = imageLines.map((e) => DockerImage.fromRawStr(e)).toList();
|
||||
} catch (e) {
|
||||
error = DockerErr(type: DockerErrType.unknown, message: e.toString());
|
||||
@@ -104,6 +111,25 @@ class DockerProvider extends BusyProvider {
|
||||
} finally {
|
||||
setBusyState(false);
|
||||
}
|
||||
|
||||
// Parse docker stats
|
||||
final statsRaw = segments[2];
|
||||
try {
|
||||
final statsLines = statsRaw.split('\n');
|
||||
statsLines.removeWhere((element) => element.isEmpty);
|
||||
statsLines.removeAt(0);
|
||||
for (var item in items!) {
|
||||
final statsLine = statsLines.firstWhere(
|
||||
(element) => element.contains(item.containerId),
|
||||
orElse: () => '',
|
||||
);
|
||||
item.parseStats(statsLine);
|
||||
}
|
||||
} catch (e) {
|
||||
error = DockerErr(type: DockerErrType.unknown, message: e.toString());
|
||||
} finally {
|
||||
setBusyState(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onPwd(String event, StreamSink<Uint8List> stdin) async {
|
||||
@@ -163,11 +189,11 @@ class DockerProvider extends BusyProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
// judge whether to use DOCKER_HOST / sudo
|
||||
// judge whether to use DOCKER_HOST
|
||||
String _wrap(String cmd) {
|
||||
final dockerHost = dockerStore.getDockerHost(hostId!);
|
||||
if (dockerHost == null || dockerHost.isEmpty) {
|
||||
return 'sudo $cmd'.withLangExport;
|
||||
return cmd.withLangExport;
|
||||
}
|
||||
return 'export DOCKER_HOST=$dockerHost && $cmd'.withLangExport;
|
||||
}
|
||||
|
||||
@@ -245,8 +245,7 @@ class ServerProvider extends BusyProvider {
|
||||
|
||||
if (s.client == null) return;
|
||||
// run script to get server status
|
||||
raw =
|
||||
await s.client!.run("sh $shellPath -${shellFuncStatus.flag}").string;
|
||||
raw = await s.client!.run(shellFuncStatus.exec).string;
|
||||
segments = raw.split(seperator).map((e) => e.trim()).toList();
|
||||
if (raw.isEmpty || segments.length != CmdType.values.length) {
|
||||
s.state = ServerState.failed;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 403;
|
||||
static const int build = 404;
|
||||
static const String engine = "3.10.6";
|
||||
static const String buildAt = "2023-08-02 19:59:01.356049";
|
||||
static const String buildAt = "2023-08-02 20:14:57.880607";
|
||||
static const int modifications = 4;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ const shellPath = '$serverBoxDir/mobile_app.sh';
|
||||
const echoPWD = 'echo \$PWD';
|
||||
|
||||
enum CmdType {
|
||||
export,
|
||||
net,
|
||||
sys,
|
||||
cpu,
|
||||
@@ -23,7 +22,6 @@ enum CmdType {
|
||||
}
|
||||
|
||||
const _cmdList = [
|
||||
'export LANG=en_US.utf-8',
|
||||
'cat /proc/net/dev && date +%s',
|
||||
'cat /etc/os-release | grep PRETTY_NAME',
|
||||
'cat /proc/stat | grep cpu',
|
||||
@@ -43,30 +41,32 @@ final shellFuncStatus = AppShellFunc(
|
||||
's',
|
||||
);
|
||||
|
||||
// Check if `htop` is installed.
|
||||
// Then app open SSH term and use `htop` or `ps` to see process.
|
||||
const shellFuncProcess = AppShellFunc(
|
||||
'process',
|
||||
'''
|
||||
if command -v htop &> /dev/null
|
||||
then
|
||||
htop
|
||||
else
|
||||
top
|
||||
fi
|
||||
''',
|
||||
'p',
|
||||
const dockerCmds = [
|
||||
'docker version',
|
||||
'docker ps -a',
|
||||
'docker stats --no-stream',
|
||||
'docker image ls',
|
||||
];
|
||||
|
||||
final shellFuncDocker = AppShellFunc(
|
||||
// `dockeR` -> avoid conflict with `docker` command
|
||||
// 以防止循环递归
|
||||
'dockeR',
|
||||
dockerCmds.join('\necho $seperator\n'),
|
||||
'd',
|
||||
);
|
||||
|
||||
final _generated = [
|
||||
shellFuncStatus,
|
||||
shellFuncProcess,
|
||||
shellFuncDocker,
|
||||
].generate;
|
||||
|
||||
final shellCmd = """
|
||||
# Script for app `${BuildData.name} v1.0.${BuildData.build}`
|
||||
# Delete this file while app is running will cause app crash
|
||||
|
||||
export LANG=en_US.utf-8
|
||||
|
||||
$_generated
|
||||
""";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user