mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
#64 new: process page
This commit is contained in:
@@ -360,7 +360,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -368,7 +368,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -491,7 +491,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -499,7 +499,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -516,7 +516,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -524,7 +524,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
123
lib/data/model/server/proc.dart
Normal file
123
lib/data/model/server/proc.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
// Models for `ps -aux`
|
||||
|
||||
// Each line
|
||||
import 'dart:convert';
|
||||
|
||||
class Proc {
|
||||
final String user;
|
||||
final int pid;
|
||||
final double cpu;
|
||||
final double mem;
|
||||
final String vsz;
|
||||
final String rss;
|
||||
final String tty;
|
||||
final String stat;
|
||||
final String start;
|
||||
final String time;
|
||||
final String command;
|
||||
|
||||
Proc({
|
||||
required this.user,
|
||||
required this.pid,
|
||||
required this.cpu,
|
||||
required this.mem,
|
||||
required this.vsz,
|
||||
required this.rss,
|
||||
required this.tty,
|
||||
required this.stat,
|
||||
required this.start,
|
||||
required this.time,
|
||||
required this.command,
|
||||
});
|
||||
|
||||
factory Proc.parse(String raw) {
|
||||
final parts = raw.split(RegExp(r'\s+'));
|
||||
return Proc(
|
||||
user: parts[0],
|
||||
pid: int.parse(parts[1]),
|
||||
cpu: double.parse(parts[2]),
|
||||
mem: double.parse(parts[3]),
|
||||
vsz: parts[4],
|
||||
rss: parts[5],
|
||||
tty: parts[6],
|
||||
stat: parts[7],
|
||||
start: parts[8],
|
||||
time: parts[9],
|
||||
command: parts.sublist(10).join(' '),
|
||||
);
|
||||
}
|
||||
|
||||
Map toJson() {
|
||||
return {
|
||||
'user': user,
|
||||
'pid': pid,
|
||||
'cpu': cpu,
|
||||
'mem': mem,
|
||||
'vsz': vsz,
|
||||
'rss': rss,
|
||||
'tty': tty,
|
||||
'stat': stat,
|
||||
'start': start,
|
||||
'time': time,
|
||||
'command': command,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return const JsonEncoder.withIndent(' ').convert(toJson());
|
||||
}
|
||||
|
||||
String get binary {
|
||||
final parts = command.split(' ');
|
||||
return parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
// `ps -aux` result
|
||||
class PsResult {
|
||||
final List<Proc> procs;
|
||||
final String? error;
|
||||
|
||||
PsResult({
|
||||
required this.procs,
|
||||
this.error,
|
||||
});
|
||||
|
||||
factory PsResult.parse(String raw, {ProcSortMode sort = ProcSortMode.cpu}) {
|
||||
final lines = raw.split('\n');
|
||||
final procs = <Proc>[];
|
||||
var err = '';
|
||||
for (var i = 1; i < lines.length; i++) {
|
||||
final line = lines[i];
|
||||
if (line.isEmpty) continue;
|
||||
try {
|
||||
procs.add(Proc.parse(line));
|
||||
} catch (e) {
|
||||
err += '$line: $e\n';
|
||||
}
|
||||
}
|
||||
switch (sort) {
|
||||
case ProcSortMode.cpu:
|
||||
procs.sort((a, b) => b.cpu.compareTo(a.cpu));
|
||||
break;
|
||||
case ProcSortMode.mem:
|
||||
procs.sort((a, b) => b.mem.compareTo(a.mem));
|
||||
break;
|
||||
case ProcSortMode.pid:
|
||||
procs.sort((a, b) => a.pid.compareTo(b.pid));
|
||||
break;
|
||||
case ProcSortMode.user:
|
||||
procs.sort((a, b) => a.user.compareTo(b.user));
|
||||
break;
|
||||
}
|
||||
return PsResult(procs: procs, error: err.isEmpty ? null : err);
|
||||
}
|
||||
}
|
||||
|
||||
enum ProcSortMode {
|
||||
cpu,
|
||||
mem,
|
||||
pid,
|
||||
user;
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 357;
|
||||
static const int build = 361;
|
||||
static const String engine = "3.10.0";
|
||||
static const String buildAt = "2023-06-08 22:45:50.770980";
|
||||
static const int modifications = 3;
|
||||
static const String buildAt = "2023-06-21 18:31:01.595350";
|
||||
static const int modifications = 2;
|
||||
}
|
||||
|
||||
139
lib/view/page/process.dart
Normal file
139
lib/view/page/process.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/extension/uint8list.dart';
|
||||
import 'package:toolbox/core/utils/ui.dart';
|
||||
import 'package:toolbox/data/model/server/proc.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/res/ui.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
import 'package:toolbox/view/widget/two_line_text.dart';
|
||||
|
||||
import '../../data/provider/server.dart';
|
||||
import '../../locator.dart';
|
||||
|
||||
class ProcessPage extends StatefulWidget {
|
||||
final ServerPrivateInfo spi;
|
||||
const ProcessPage({super.key, required this.spi});
|
||||
|
||||
@override
|
||||
_ProcessPageState createState() => _ProcessPageState();
|
||||
}
|
||||
|
||||
class _ProcessPageState extends State<ProcessPage> {
|
||||
late S _s;
|
||||
late Timer _timer;
|
||||
|
||||
PsResult _result = PsResult(procs: []);
|
||||
int? _lastFocusId;
|
||||
ProcSortMode _procSortMode = ProcSortMode.cpu;
|
||||
|
||||
final _serverProvider = locator<ServerProvider>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final client = _serverProvider.servers[widget.spi.id]?.client;
|
||||
if (client == null) {
|
||||
showSnackBar(context, Text(_s.noClient));
|
||||
return;
|
||||
}
|
||||
_timer = Timer.periodic(const Duration(seconds: 3), (_) async {
|
||||
if (mounted) {
|
||||
final result = await client.run('ps -aux'.withLangExport).string;
|
||||
if (result.isEmpty) {
|
||||
showSnackBar(context, Text(_s.noResult));
|
||||
return;
|
||||
}
|
||||
_result = PsResult.parse(result, sort: _procSortMode);
|
||||
setState(() {});
|
||||
} else {
|
||||
_timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_timer.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_s = S.of(context)!;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actions = <Widget>[
|
||||
PopupMenuButton<ProcSortMode>(
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
_procSortMode = value;
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
initialValue: _procSortMode,
|
||||
itemBuilder: (_) => ProcSortMode.values
|
||||
.map(
|
||||
(e) => PopupMenuItem(value: e, child: Text(e.name)),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
];
|
||||
if (_result.error != null) {
|
||||
actions.add(IconButton(
|
||||
icon: const Icon(Icons.error),
|
||||
onPressed: () => showRoundDialog(
|
||||
context: context,
|
||||
child: Text(_result.error!),
|
||||
),
|
||||
));
|
||||
}
|
||||
Widget child;
|
||||
if (_result.procs.isEmpty) {
|
||||
child = centerLoading;
|
||||
} else {
|
||||
child = ListView.builder(
|
||||
itemCount: _result.procs.length,
|
||||
itemBuilder: (ctx, idx) {
|
||||
final proc = _result.procs[idx];
|
||||
return _buildListItem(proc);
|
||||
},
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: TwoLineText(up: widget.spi.name, down: _s.process),
|
||||
actions: actions,
|
||||
),
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListItem(Proc proc) {
|
||||
return RoundRectCard(ListTile(
|
||||
leading: SizedBox(
|
||||
width: 57,
|
||||
child: TwoLineText(up: proc.pid.toString(), down: 'pid'),
|
||||
),
|
||||
title: Text(proc.binary),
|
||||
subtitle: Text(proc.command, style: grey),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TwoLineText(up: proc.cpu.toStringAsFixed(1), down: 'cpu'),
|
||||
width13,
|
||||
TwoLineText(up: proc.mem.toStringAsFixed(1), down: 'mem'),
|
||||
],
|
||||
),
|
||||
onTap: () => _lastFocusId = proc.pid,
|
||||
autofocus: _lastFocusId == proc.pid,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/extension/navigator.dart';
|
||||
import 'package:toolbox/core/extension/order.dart';
|
||||
import 'package:toolbox/core/utils/misc.dart';
|
||||
import 'package:toolbox/data/res/server_cmd.dart';
|
||||
import 'package:toolbox/view/page/process.dart';
|
||||
|
||||
import '../../../core/route.dart';
|
||||
import '../../../core/utils/ui.dart';
|
||||
@@ -338,13 +338,14 @@ class _ServerPageState extends State<ServerPage>
|
||||
AppRoute(DockerManagePage(spi), 'Docker manage').go(context);
|
||||
break;
|
||||
case ServerTabMenuType.process:
|
||||
AppRoute(
|
||||
SSHPage(
|
||||
spi: spi,
|
||||
initCmd: 'sh $shellPath -${shellFuncProcess.flag}',
|
||||
),
|
||||
'ssh page (process)',
|
||||
).go(context);
|
||||
// AppRoute(
|
||||
// SSHPage(
|
||||
// spi: spi,
|
||||
// initCmd: 'sh $shellPath -${shellFuncProcess.flag}',
|
||||
// ),
|
||||
// 'ssh page (process)',
|
||||
// ).go(context);
|
||||
AppRoute(ProcessPage(spi: spi), 'process page').go(context);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,14 +10,18 @@ class TwoLineText extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
up,
|
||||
style: textSize15,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
down,
|
||||
style: textSize11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -475,9 +475,9 @@
|
||||
baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -490,9 +490,9 @@
|
||||
baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -505,9 +505,9 @@
|
||||
baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
CURRENT_PROJECT_VERSION = 361;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0.357;
|
||||
MARKETING_VERSION = 1.0.361;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
Reference in New Issue
Block a user