This commit is contained in:
lollipopkit
2023-08-28 19:26:17 +08:00
parent d06ffdacd0
commit d9c26e01f4
3 changed files with 130 additions and 51 deletions

View File

@@ -43,8 +43,7 @@ enum AppShellFuncType {
switch (this) { switch (this) {
case AppShellFuncType.status: case AppShellFuncType.status:
return ''' return '''
result=\$(uname 2>&1 | grep "Linux") if [ "\$isLinux" != "" ]; then
if [ "\$result" != "" ]; then
\t${_statusCmds.join(_cmdDivider)} \t${_statusCmds.join(_cmdDivider)}
else else
\t${_bsdStatusCmd.join(_cmdDivider)} \t${_bsdStatusCmd.join(_cmdDivider)}
@@ -59,23 +58,16 @@ else
fi'''; fi''';
case AppShellFuncType.process: case AppShellFuncType.process:
return ''' return '''
# Try sequencially if [ "\$isLinux" != "" ]; then
# main Linux: `ps -aux` \tif [ "\$isBusybox" != "" ]; then
# BSD: `ps -ax` \t\tps w
# alpine: `ps -o pid,user,time,args` \telse
# \t\tps -aux
# If there is any error, try another one \tfi
result=\$(ps -aux 2>&1 | grep "ps: ")
if [ "\$result" = "" ]; then
ps -aux
else else
result=\$(ps -ax 2>&1 | grep "ps: ") \tps -ax
if [ "\$result" = "" ]; then
ps -ax
else
ps -o pid,user,time,args
fi fi
fi'''; ''';
} }
} }
@@ -201,6 +193,10 @@ final _shellCmd = """
export LANG=en_US.UTF-8 export LANG=en_US.UTF-8
isLinux=\$(uname 2>&1 | grep "Linux")
# Link /bin/sh to busybox?
isBusybox=\$(ls -l /bin/sh | grep "busybox")
${AppShellFuncType.shellScript} ${AppShellFuncType.shellScript}
"""; """;

View File

@@ -1,8 +1,40 @@
import 'package:logging/logging.dart';
import '../../../data/res/misc.dart'; import '../../../data/res/misc.dart';
final _logger = Logger('Proc');
class _ProcValIdxMap {
final int pid;
final int? user;
final int? cpu;
final int? mem;
final int? vsz;
final int? rss;
final int? tty;
final int? stat;
final int? start;
final int? time;
final int command;
_ProcValIdxMap({
required this.pid,
this.user,
this.cpu,
this.mem,
this.vsz,
this.rss,
this.tty,
this.stat,
this.start,
this.time,
required this.command,
});
}
/// Some field can be null due to incompatible format on `BSD` and `Alpine` /// Some field can be null due to incompatible format on `BSD` and `Alpine`
class Proc { class Proc {
final String user; final String? user;
final int pid; final int pid;
final double? cpu; final double? cpu;
final double? mem; final double? mem;
@@ -11,37 +43,37 @@ class Proc {
final String? tty; final String? tty;
final String? stat; final String? stat;
final String? start; final String? start;
final String time; final String? time;
final String command; final String command;
Proc({ Proc({
required this.user, this.user,
required this.pid, required this.pid,
required this.cpu, this.cpu,
required this.mem, this.mem,
required this.vsz, this.vsz,
required this.rss, this.rss,
required this.tty, this.tty,
required this.stat, this.stat,
required this.start, this.start,
required this.time, this.time,
required this.command, required this.command,
}); });
factory Proc.parse(String raw) { factory Proc.parse(String raw, _ProcValIdxMap map) {
final parts = raw.split(RegExp(r'\s+')); final parts = raw.split(RegExp(r'\s+'));
return Proc( return Proc(
user: parts[0], user: map.user == null ? null : parts[map.user!],
pid: int.parse(parts[1]), pid: int.parse(parts[map.pid]),
cpu: double.parse(parts[2]), cpu: map.cpu == null ? null : double.parse(parts[map.cpu!]),
mem: double.parse(parts[3]), mem: map.mem == null ? null : double.parse(parts[map.mem!]),
vsz: parts[4], vsz: map.vsz == null ? null : parts[map.vsz!],
rss: parts[5], rss: map.rss == null ? null : parts[map.rss!],
tty: parts[6], tty: map.tty == null ? null : parts[map.tty!],
stat: parts[7], stat: map.stat == null ? null : parts[map.stat!],
start: parts[8], start: map.start == null ? null : parts[map.start!],
time: parts[9], time: map.time == null ? null : parts[map.time!],
command: parts.sublist(10).join(' '), command: parts.sublist(map.command).join(' '),
); );
} }
@@ -84,17 +116,38 @@ class PsResult {
factory PsResult.parse(String raw, {ProcSortMode sort = ProcSortMode.cpu}) { factory PsResult.parse(String raw, {ProcSortMode sort = ProcSortMode.cpu}) {
final lines = raw.split('\n'); final lines = raw.split('\n');
if (lines.isEmpty) return PsResult(procs: [], error: null);
final header = lines[0];
final parts = header.split(RegExp(r'\s+'));
parts.removeWhere((element) => element.isEmpty);
final map = _ProcValIdxMap(
pid: parts.indexOfOrNull('PID')!,
user: parts.indexOfOrNull('USER'),
cpu: parts.indexOfOrNull('%CPU'),
mem: parts.indexOfOrNull('%MEM'),
vsz: parts.indexOfOrNull('VSZ'),
rss: parts.indexOfOrNull('RSS'),
tty: parts.indexOfOrNull('TTY'),
stat: parts.indexOfOrNull('STAT'),
start: parts.indexOfOrNull('START'),
time: parts.indexOfOrNull('TIME'),
command: parts.indexOfOrNull('COMMAND') ?? parts.indexOfOrNull('CMD')!,
);
final procs = <Proc>[]; final procs = <Proc>[];
var err = ''; var err = '';
for (var i = 1; i < lines.length; i++) { for (var i = 1; i < lines.length; i++) {
final line = lines[i]; final line = lines[i];
if (line.isEmpty) continue; if (line.isEmpty) continue;
try { try {
procs.add(Proc.parse(line)); procs.add(Proc.parse(line, map));
} catch (e) { } catch (e, trace) {
err += '$line: $e\n'; err += '$line: $e\n';
_logger.warning(trace);
} }
} }
switch (sort) { switch (sort) {
case ProcSortMode.cpu: case ProcSortMode.cpu:
procs.sort((a, b) => b.cpu?.compareTo(a.cpu ?? 0) ?? 0); procs.sort((a, b) => b.cpu?.compareTo(a.cpu ?? 0) ?? 0);
@@ -106,7 +159,7 @@ class PsResult {
procs.sort((a, b) => a.pid.compareTo(b.pid)); procs.sort((a, b) => a.pid.compareTo(b.pid));
break; break;
case ProcSortMode.user: case ProcSortMode.user:
procs.sort((a, b) => a.user.compareTo(b.user)); procs.sort((a, b) => a.user?.compareTo(b.user ?? '') ?? 0);
break; break;
case ProcSortMode.name: case ProcSortMode.name:
procs.sort((a, b) => a.binary.compareTo(b.binary)); procs.sort((a, b) => a.binary.compareTo(b.binary));
@@ -124,3 +177,10 @@ enum ProcSortMode {
name, name,
; ;
} }
extension _StrIndex on List<String> {
int? indexOfOrNull(String val) {
final idx = indexOf(val);
return idx == -1 ? null : idx;
}
}

View File

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/context.dart';
import 'package:toolbox/core/extension/uint8list.dart'; import 'package:toolbox/core/extension/uint8list.dart';
import 'package:toolbox/core/utils/misc.dart';
import '../../core/utils/ui.dart'; import '../../core/utils/ui.dart';
import '../../data/model/app/shell_func.dart'; import '../../data/model/app/shell_func.dart';
@@ -12,6 +13,7 @@ import '../../data/model/server/proc.dart';
import '../../data/model/server/server_private_info.dart'; import '../../data/model/server/server_private_info.dart';
import '../../data/provider/server.dart'; import '../../data/provider/server.dart';
import '../../data/res/ui.dart'; import '../../data/res/ui.dart';
import '../../data/store/setting.dart';
import '../../locator.dart'; import '../../locator.dart';
import '../widget/custom_appbar.dart'; import '../widget/custom_appbar.dart';
import '../widget/round_rect_card.dart'; import '../widget/round_rect_card.dart';
@@ -30,6 +32,8 @@ class _ProcessPageState extends State<ProcessPage> {
late Timer _timer; late Timer _timer;
late MediaQueryData _media; late MediaQueryData _media;
final _setting = locator<SettingStore>();
SSHClient? _client; SSHClient? _client;
PsResult _result = PsResult(procs: []); PsResult _result = PsResult(procs: []);
@@ -39,7 +43,7 @@ class _ProcessPageState extends State<ProcessPage> {
// In cpu mode, the process list will change in a high frequency. // In cpu mode, the process list will change in a high frequency.
// So user will easily know that the list is refreshed. // So user will easily know that the list is refreshed.
ProcSortMode _procSortMode = ProcSortMode.cpu; ProcSortMode _procSortMode = ProcSortMode.cpu;
List<ProcSortMode> _sortModes = ProcSortMode.values; List<ProcSortMode> _sortModes = List.from(ProcSortMode.values);
final _serverProvider = locator<ServerProvider>(); final _serverProvider = locator<ServerProvider>();
@@ -47,7 +51,9 @@ class _ProcessPageState extends State<ProcessPage> {
void initState() { void initState() {
super.initState(); super.initState();
_client = _serverProvider.servers[widget.spi.id]?.client; _client = _serverProvider.servers[widget.spi.id]?.client;
_timer = Timer.periodic(const Duration(seconds: 3), (_) => _refresh()); final duration =
Duration(seconds: _setting.serverStatusUpdateInterval.fetch());
_timer = Timer.periodic(duration, (_) => _refresh());
} }
@override @override
@@ -110,7 +116,13 @@ class _ProcessPageState extends State<ProcessPage> {
onPressed: () => showRoundDialog( onPressed: () => showRoundDialog(
context: context, context: context,
title: Text(_s.error), title: Text(_s.error),
child: Text(_result.error!), child: SingleChildScrollView(child: Text(_result.error!)),
actions: [
TextButton(
onPressed: () => copy2Clipboard(_result.error!),
child: Text(_s.copy),
),
],
), ),
)); ));
} }
@@ -135,11 +147,14 @@ class _ProcessPageState extends State<ProcessPage> {
} }
Widget _buildListItem(Proc proc) { Widget _buildListItem(Proc proc) {
final leading = proc.user == null
? Text(proc.pid.toString())
: TwoLineText(up: proc.pid.toString(), down: proc.user!);
return RoundRectCard( return RoundRectCard(
ListTile( ListTile(
leading: SizedBox( leading: SizedBox(
width: _media.size.width / 6, width: _media.size.width / 6,
child: TwoLineText(up: proc.pid.toString(), down: proc.user), child: leading,
), ),
title: Text(proc.binary), title: Text(proc.binary),
subtitle: Text( subtitle: Text(
@@ -175,15 +190,23 @@ class _ProcessPageState extends State<ProcessPage> {
} }
Widget? _buildItemTrail(Proc proc) { Widget? _buildItemTrail(Proc proc) {
if (proc.cpu == null || proc.mem == null) { if (proc.cpu == null && proc.mem == null) {
return null; return null;
} }
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TwoLineText(up: proc.cpu!.toStringAsFixed(1), down: 'cpu'), if (proc.cpu != null)
TwoLineText(
up: proc.cpu!.toStringAsFixed(1),
down: 'cpu',
),
width13, width13,
TwoLineText(up: proc.mem!.toStringAsFixed(1), down: 'mem'), if (proc.mem != null)
TwoLineText(
up: proc.mem!.toStringAsFixed(1),
down: 'mem',
),
], ],
); );
} }