new: sensors view (#285)

This commit is contained in:
lollipopkit
2024-02-23 12:33:42 +08:00
parent fad3f41e58
commit 1f586a2c31
23 changed files with 348 additions and 34 deletions

View File

@@ -258,5 +258,11 @@ class PropertyListenable<T> extends ValueListenable<T> {
}
@override
T get value => box.get(key, defaultValue: defaultValue);
T get value {
final val = box.get(key, defaultValue: defaultValue);
if (val == null || val is! T) {
return defaultValue!;
}
return val;
}
}

View File

@@ -200,6 +200,7 @@ enum StatusCmdType {
diskio,
battery,
nvidia,
sensors,
;
}
@@ -220,6 +221,7 @@ const _statusCmds = [
'cat /proc/diskstats',
'for f in /sys/class/power_supply/*/uevent; do cat "\$f"; echo; done',
'nvidia-smi -q -x',
'sensors -j',
];
enum BSDStatusCmdType {

View File

@@ -0,0 +1,175 @@
import 'dart:convert';
import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/data/res/logger.dart';
final class SensorAdaptor {
final String raw;
const SensorAdaptor(this.raw);
static const acpiRaw = 'ACPI interface';
static const pciRaw = 'PCI adapter';
static const virtualRaw = 'Virtual device';
static const isaRaw = 'ISA adapter';
static const acpi = SensorAdaptor(acpiRaw);
static const pci = SensorAdaptor(pciRaw);
static const virtual = SensorAdaptor(virtualRaw);
static const isa = SensorAdaptor(isaRaw);
static SensorAdaptor parse(String raw) => switch (raw) {
acpiRaw => acpi,
pciRaw => pci,
virtualRaw => virtual,
isaRaw => isa,
_ => SensorAdaptor(raw),
};
}
final class SensorTemp {
final double? current;
final double? max;
final double? min;
const SensorTemp({this.current, this.max, this.min});
@override
String toString() {
return 'SensorTemp{current: $current, max: $max, min: $min}';
}
@override
operator ==(Object other) {
if (identical(this, other)) return true;
return other is SensorTemp &&
other.current == current &&
other.max == max &&
other.min == min;
}
@override
int get hashCode => current.hashCode ^ max.hashCode ^ min.hashCode;
}
final class SensorItem {
final String device;
final SensorAdaptor adapter;
final Map<String, SensorTemp> props;
const SensorItem({
required this.device,
required this.adapter,
required this.props,
});
static List<SensorItem> parse(String raw) {
final rmErrRaw = raw.split('\n')
..removeWhere((element) => element.contains('ERROR:'));
final map = json.decode(rmErrRaw.join('\n')) as Map<String, dynamic>;
final items = <SensorItem>[];
for (final key in map.keys) {
try {
final adapter = SensorAdaptor.parse(map[key]['Adapter'] as String);
final props = <String, SensorTemp>{};
for (final subKey in map[key].keys) {
if (subKey == 'Adapter') {
continue;
}
final subMap = map[key][subKey] as Map<String, dynamic>;
final currentKey =
subMap.keys.toList().firstWhereOrNull((e) => e.endsWith('input'));
final current = subMap[currentKey] as double?;
final maxKey = subMap.keys
.toList()
.firstWhereOrNull((e) => e.endsWith('max') || e.endsWith('crit'));
final max = subMap[maxKey] as double?;
final minKey =
subMap.keys.toList().firstWhereOrNull((e) => e.endsWith('min'));
final min = subMap[minKey] as double?;
if (current == null && max == null && min == null) {
continue;
}
props[subKey] = SensorTemp(current: current, max: max, min: min);
}
items.add(SensorItem(device: key, adapter: adapter, props: props));
} catch (e, s) {
Loggers.parse.warning(e, s);
continue;
}
}
return items;
}
static const sensorsRaw = '''
{
"coretemp-isa-0000":{
"Adapter": "ISA adapter",
"Package id 0":{
"temp1_input": 51.000,
"temp1_max": 105.000,
"temp1_crit": 105.000,
"temp1_crit_alarm": 0.000
},
"Core 0":{
"temp2_input": 40.000,
"temp2_max": 105.000,
"temp2_crit": 105.000,
"temp2_crit_alarm": 0.000
},
"Core 1":{
"temp3_input": 40.000,
"temp3_max": 105.000,
"temp3_crit": 105.000,
"temp3_crit_alarm": 0.000
},
"Core 2":{
"temp4_input": 40.000,
"temp4_max": 105.000,
"temp4_crit": 105.000,
"temp4_crit_alarm": 0.000
},
"Core 3":{
"temp5_input": 40.000,
"temp5_max": 105.000,
"temp5_crit": 105.000,
"temp5_crit_alarm": 0.000
}
},
"acpitz-acpi-0":{
"Adapter": "ACPI interface",
"temp1":{
"temp1_input": 27.800,
"temp1_crit": 119.000
}
},
"iwlwifi_1-virtual-0":{
"Adapter": "Virtual device",
"temp1":{
ERROR: Can't get value of subfeature temp1_input: Can't read
}
},
"nvme-pci-0400":{
"Adapter": "PCI adapter",
"Composite":{
"temp1_input": 35.850,
"temp1_max": 83.850,
"temp1_min": -273.150,
"temp1_crit": 84.850,
"temp1_alarm": 0.000
},
"Sensor 1":{
"temp2_input": 35.850,
"temp2_max": 65261.850,
"temp2_min": -273.150
},
"Sensor 2":{
"temp3_input": 37.850,
"temp3_max": 65261.850,
"temp3_min": -273.150
}
}
}
''';
}

View File

@@ -7,6 +7,7 @@ import 'package:toolbox/data/model/server/disk.dart';
import 'package:toolbox/data/model/server/memory.dart';
import 'package:toolbox/data/model/server/net_speed.dart';
import 'package:toolbox/data/model/server/nvdia.dart';
import 'package:toolbox/data/model/server/sensors.dart';
import 'package:toolbox/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/system.dart';
import 'package:toolbox/data/model/server/temp.dart';
@@ -55,6 +56,7 @@ class ServerStatus {
List<NvidiaSmiItem>? nvidia;
final List<Battery> batteries = [];
final Map<StatusCmdType, String> more = {};
final List<SensorItem> sensors = [];
ServerStatus({
required this.cpu,

View File

@@ -1,5 +1,6 @@
import 'package:toolbox/data/model/server/battery.dart';
import 'package:toolbox/data/model/server/nvdia.dart';
import 'package:toolbox/data/model/server/sensors.dart';
import 'package:toolbox/data/model/server/server.dart';
import 'package:toolbox/data/model/server/system.dart';
import 'package:toolbox/data/res/logger.dart';
@@ -143,6 +144,16 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
Loggers.parse.warning(e, s);
}
try {
final sensors = SensorItem.parse(StatusCmdType.sensors.find(segments));
if (sensors.isNotEmpty) {
req.ss.sensors.clear();
req.ss.sensors.addAll(sensors);
}
} catch (e, s) {
Loggers.parse.warning(e, s);
}
return req.ss;
}

View File

@@ -2,9 +2,9 @@
class BuildData {
static const String name = "ServerBox";
static const int build = 773;
static const String engine = "3.19.1";
static const String buildAt = "2024-02-22 11:55:07";
static const int modifications = 8;
static const int build = 775;
static const String engine = "3.19.0";
static const String buildAt = "2024-02-23 09:28:28";
static const int modifications = 2;
static const int script = 38;
}

View File

@@ -12,6 +12,7 @@ abstract final class Defaults {
'gpu',
'disk',
'net',
'sensor',
'temp',
'battery'
];

View File

@@ -199,6 +199,7 @@
"save": "Speichern",
"saved": "Gerettet",
"second": "s",
"sensors": "Sensor",
"sequence": "Sequenz",
"server": "Server",
"serverDetailOrder": "Reihenfolge der Widgets auf der Detailseite",

View File

@@ -199,6 +199,7 @@
"save": "Save",
"saved": "Saved",
"second": "s",
"sensors": "Sensor",
"sequence": "Sequence",
"server": "Server",
"serverDetailOrder": "Detail page widget order",

View File

@@ -199,6 +199,7 @@
"save": "Enregistrer",
"saved": "Enregistré",
"second": "s",
"sensors": "Capteur",
"sequence": "Séquence",
"server": "Serveur",
"serverDetailOrder": "Ordre des widgets de la page de détails",

View File

@@ -199,6 +199,7 @@
"save": "Menyimpan",
"saved": "Diselamatkan",
"second": "S",
"sensors": "Sensor",
"sequence": "Urutan",
"server": "Server",
"serverDetailOrder": "Detail pesanan widget halaman",

View File

@@ -199,6 +199,7 @@
"save": "保存",
"saved": "已保存",
"second": "秒",
"sensors": "传感器",
"sequence": "顺序",
"server": "服务器",
"serverDetailOrder": "详情页部件顺序",

View File

@@ -199,6 +199,7 @@
"save": "保存",
"saved": "已保存",
"second": "秒",
"sensors": "传感器",
"sequence": "順序",
"server": "服務器",
"serverDetailOrder": "詳情頁部件順序",

View File

@@ -10,6 +10,7 @@ import 'package:toolbox/data/model/server/cpu.dart';
import 'package:toolbox/data/model/server/disk.dart';
import 'package:toolbox/data/model/server/net_speed.dart';
import 'package:toolbox/data/model/server/nvdia.dart';
import 'package:toolbox/data/model/server/sensors.dart';
import 'package:toolbox/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/system.dart';
import 'package:toolbox/data/res/store.dart';
@@ -47,6 +48,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
_buildGpuView,
_buildDiskView,
_buildNetView,
_buildSensors,
_buildTemperature,
_buildBatteries,
],
@@ -316,7 +318,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
}
Widget _buildGpuView(ServerStatus ss) {
if (ss.nvidia == null) return UIs.placeholder;
if (ss.nvidia == null || ss.nvidia?.isEmpty == true) return UIs.placeholder;
final children = ss.nvidia?.map((e) => _buildGpuItem(e)).toList() ?? [];
return CardX(
child: ExpandTile(
@@ -331,10 +333,6 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Widget _buildGpuItem(NvidiaSmiItem item) {
final mem = item.memory;
final processes = mem.processes;
final children = <Widget>[];
if (processes.isNotEmpty) {
children.addAll(processes.map((e) => _buildGpuProcessItem(e)));
}
return ListTile(
title: Text(item.name, style: UIs.text13),
leading: Text(
@@ -664,6 +662,51 @@ class _ServerDetailPageState extends State<ServerDetailPage>
);
}
Widget _buildSensors(ServerStatus ss) {
if (ss.sensors.isEmpty) return UIs.placeholder;
return CardX(
child: ExpandTile(
title: Text(l10n.sensors),
leading: const Icon(Icons.thermostat, size: 17),
childrenPadding: const EdgeInsets.only(bottom: 7),
initiallyExpanded: _getInitExpand(ss.sensors.length, 3),
children: ss.sensors.map(_buildSensorItem).toList(),
),
);
}
Widget _buildSensorItem(SensorItem si) {
if (si.props.isEmpty) return UIs.placeholder;
return ListTile(
title: Text(si.device, style: UIs.text15),
subtitle: Column(
children: si.props.keys
.map((e) => _buildSensorDetailItem(e, si.props[e]))
.toList(),
),
);
}
Widget _buildSensorDetailItem(String key, SensorTemp? st) {
if (st == null) return UIs.placeholder;
final text = () {
final current = st.current?.toStringAsFixed(1);
final max = st.max?.toStringAsFixed(1);
final min = st.min?.toStringAsFixed(1);
final currentText = current == null ? '' : '$current°C';
final maxText = max == null ? '' : ' | ${l10n.max}:$max';
final minText = min == null ? '' : ' | ${l10n.min}:$min';
return '$currentText$maxText$minText';
}();
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(key, style: UIs.text13),
Text(text, style: UIs.text13Grey),
],
);
}
Widget _buildAnimatedText(Key key, String text, TextStyle style) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 277),