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 { late S _s; late Timer _timer; PsResult _result = PsResult(procs: []); int? _lastFocusId; ProcSortMode _procSortMode = ProcSortMode.mem; final _serverProvider = locator(); @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 = [ PopupMenuButton( 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, padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 7), 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: proc.user), ), title: Text(proc.binary), subtitle: Text(proc.command, style: grey, maxLines: 3, overflow: TextOverflow.fade,), 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, )); } }