Files
flutter_server_box/lib/view/page/server/detail/misc.dart
lollipopkit🏳️‍⚧️ 3a615449e3 feat: Windows compatibility (#836)
* feat: win compatibility

* fix

* fix: uptime parse

* opt.: linux uptime accuracy

* fix: windows temperature fetching

* opt.

* opt.: powershell exec

* refactor: address PR review feedback and improve code quality

### Major Improvements:
- **Refactored Windows status parsing**: Broke down large `_getWindowsStatus` method into 13 smaller, focused helper methods for better maintainability and readability
- **Extracted system detection logic**: Created dedicated `SystemDetector` helper class to separate OS detection concerns from ServerProvider
- **Improved concurrency handling**: Implemented proper synchronization for server updates using Future-based locks to prevent race conditions

### Bug Fixes:
- **Fixed CPU percentage parsing**: Removed incorrect '*100' multiplication in BSD CPU parsing (values were already percentages)
- **Enhanced memory parsing**: Added validation and error handling to BSD memory fallback parsing with proper logging
- **Improved uptime parsing**: Added support for multiple Windows date formats and robust error handling with validation
- **Fixed division by zero**: Added safety checks in Swap.usedPercent getter

### Code Quality Enhancements:
- **Added comprehensive documentation**: Documented Windows CPU counter limitations and approach
- **Strengthened error handling**: Added detailed logging and validation throughout parsing methods
- **Improved robustness**: Enhanced BSD CPU parsing with percentage validation and warnings
- **Better separation of concerns**: Each parsing method now has single responsibility

### Files Changed:
- `lib/data/helper/system_detector.dart` (new): System detection helper
- `lib/data/model/server/cpu.dart`: Fixed percentage parsing and added validation
- `lib/data/model/server/memory.dart`: Enhanced fallback parsing and division-by-zero protection
- `lib/data/model/server/server_status_update_req.dart`: Refactored into 13 focused parsing methods
- `lib/data/provider/server.dart`: Improved synchronization and extracted system detection

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: parse & shell fn struct

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-08 16:56:36 +08:00

211 lines
6.2 KiB
Dart

part of 'view.dart';
extension on _ServerDetailPageState {
void _onTapNvidiaGpuItem(NvidiaSmiItem item) {
final processes = item.memory.processes;
final displayCount = processes.length > 5 ? 5 : processes.length;
final height = displayCount * 47.0;
context.showRoundDialog(
title: item.name,
child: SizedBox(
width: double.maxFinite,
height: height,
child: ListView.builder(
itemCount: processes.length,
itemBuilder: (_, idx) => _buildGpuProcessItem(processes[idx]),
),
),
actions: Btnx.oks,
);
}
void _onTapAmdGpuItem(AmdSmiItem item) {
final processes = item.memory.processes;
final displayCount = processes.length > 5 ? 5 : processes.length;
final height = displayCount * 47.0;
context.showRoundDialog(
title: item.name,
child: SizedBox(
width: double.maxFinite,
height: height,
child: ListView.builder(
itemCount: processes.length,
itemBuilder: (_, idx) => _buildAmdGpuProcessItem(processes[idx]),
),
),
actions: Btnx.oks,
);
}
void _onTapGpuProcessItem(NvidiaSmiMemProcess process) {
context.showRoundDialog(
title: '${process.pid}',
titleMaxLines: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
UIs.height13,
Text('Memory: ${process.memory} MiB'),
UIs.height13,
Text('Process: ${process.name}'),
],
),
actions: [TextButton(onPressed: () => context.pop(), child: Text(libL10n.close))],
);
}
void _onTapAmdGpuProcessItem(AmdSmiMemProcess process) {
context.showRoundDialog(
title: '${process.pid}',
titleMaxLines: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
UIs.height13,
Text('Memory: ${process.memory} ${process.memory > 1024 ? 'MB' : 'KB'}'),
UIs.height13,
Text('Process: ${process.name}'),
],
),
actions: [TextButton(onPressed: () => context.pop(), child: Text(libL10n.close))],
);
}
void _onTapCustomItem(MapEntry<String, String> cmd) {
context.showRoundDialog(
title: cmd.key,
child: SingleChildScrollView(child: Text(cmd.value, style: UIs.text13Grey)),
actions: [TextButton(onPressed: () => context.pop(), child: Text(libL10n.close))],
);
}
void _onTapSensorItem(SensorItem si) {
context.showRoundDialog(
title: si.device,
child: SingleChildScrollView(
child: SimpleMarkdown(
data: si.toMarkdown,
styleSheet: MarkdownStyleSheet(
tableBorder: TableBorder.all(color: Colors.grey),
tableHead: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
);
}
void _onTapTemperatureItem(String key) {
Pfs.copy(key);
context.showSnackBar('${libL10n.copy} ${libL10n.success}');
}
bool _getInitExpand(int len, [int? max]) {
if (!_collapse) return true;
if (_size.width > UIs.columnWidth) return true;
return len > 0 && len <= (max ?? 3);
}
}
enum _NetSortType {
device,
trans,
recv;
bool get isDevice => this == _NetSortType.device;
bool get isIn => this == _NetSortType.recv;
bool get isOut => this == _NetSortType.trans;
_NetSortType get next {
switch (this) {
case device:
return trans;
case _NetSortType.trans:
return recv;
case recv:
return device;
}
}
int Function(String, String) getSortFunc(NetSpeed ns) {
switch (this) {
case _NetSortType.device:
return (b, a) => a.compareTo(b);
case _NetSortType.recv:
return (b, a) => ns.speedInBytes(ns.deviceIdx(a)).compareTo(ns.speedInBytes(ns.deviceIdx(b)));
case _NetSortType.trans:
return (b, a) => ns.speedOutBytes(ns.deviceIdx(a)).compareTo(ns.speedOutBytes(ns.deviceIdx(b)));
}
}
}
Widget _buildLineChart(
List<List<FlSpot>> spots, {
String? tooltipPrefix,
bool curve = false,
int verticalInterval = 20,
}) {
return LineChart(
LineChartData(
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
tooltipPadding: const EdgeInsets.all(5),
tooltipBorderRadius: BorderRadius.circular(8),
getTooltipItems: (List<LineBarSpot> touchedSpots) {
return touchedSpots.map((e) {
return LineTooltipItem(
'$tooltipPrefix${e.barIndex}: ${e.y.toStringAsFixed(2)}',
const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
);
}).toList();
},
),
handleBuiltInTouches: true,
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: verticalInterval.toDouble(),
getDrawingHorizontalLine: (value) {
return const FlLine(color: Color.fromARGB(43, 88, 91, 94), strokeWidth: 1);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 20,
getTitlesWidget: (val, meta) {
if (val % verticalInterval != 0) return UIs.placeholder;
if (val == 0) return const Text('0 %', style: UIs.text12Grey);
return Text(val.toInt().toString(), style: UIs.text12Grey);
},
reservedSize: 27,
),
),
),
borderData: FlBorderData(show: false),
minY: -1,
maxY: 101,
lineBarsData: spots
.map(
(e) => LineChartBarData(
spots: e,
isCurved: curve,
barWidth: 2,
isStrokeCapRound: true,
color: UIs.primaryColor,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
),
)
.toList(),
),
);
}