mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
#148 fix
This commit is contained in:
@@ -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
|
fi
|
||||||
ps -ax
|
''';
|
||||||
else
|
|
||||||
ps -o pid,user,time,args
|
|
||||||
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}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user