#48 display all support devices temperature

This commit is contained in:
lollipopkit
2023-05-25 16:44:26 +08:00
parent 29683572b9
commit 46350b7522
27 changed files with 245 additions and 157 deletions

View File

@@ -443,6 +443,12 @@ abstract class S {
/// **'Files'** /// **'Files'**
String get files; String get files;
/// No description provided for @font.
///
/// In en, this message translates to:
/// **'Font'**
String get font;
/// No description provided for @fontSize. /// No description provided for @fontSize.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1007,11 +1013,11 @@ abstract class S {
/// **'Are you sure to delete server [{server}]?'** /// **'Are you sure to delete server [{server}]?'**
String sureToDeleteServer(Object server); String sureToDeleteServer(Object server);
/// No description provided for @termTheme. /// No description provided for @theme.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Terminal theme'** /// **'Theme'**
String get termTheme; String get theme;
/// No description provided for @themeMode. /// No description provided for @themeMode.
/// ///

View File

@@ -192,6 +192,9 @@ class SDe extends S {
@override @override
String get files => 'Dateien'; String get files => 'Dateien';
@override
String get font => 'Schriftarten';
@override @override
String get fontSize => 'Schriftgröße'; String get fontSize => 'Schriftgröße';
@@ -495,7 +498,7 @@ class SDe extends S {
} }
@override @override
String get termTheme => 'Farbschema des Terminals'; String get theme => 'Themen';
@override @override
String get themeMode => 'Thememodus'; String get themeMode => 'Thememodus';

View File

@@ -192,6 +192,9 @@ class SEn extends S {
@override @override
String get files => 'Files'; String get files => 'Files';
@override
String get font => 'Font';
@override @override
String get fontSize => 'Font size'; String get fontSize => 'Font size';
@@ -495,7 +498,7 @@ class SEn extends S {
} }
@override @override
String get termTheme => 'Terminal theme'; String get theme => 'Theme';
@override @override
String get themeMode => 'Theme mode'; String get themeMode => 'Theme mode';

View File

@@ -192,6 +192,9 @@ class SZh extends S {
@override @override
String get files => '文件'; String get files => '文件';
@override
String get font => '字体';
@override @override
String get fontSize => '字体大小'; String get fontSize => '字体大小';
@@ -495,7 +498,7 @@ class SZh extends S {
} }
@override @override
String get termTheme => '终端主题'; String get theme => '主题';
@override @override
String get themeMode => '主题模式'; String get themeMode => '主题模式';

View File

@@ -18,7 +18,7 @@ English | [简体中文](README_zh.md)
</p> </p>
<p align="center"> <p align="center">
A Flutter project which provide charts to display server status and tools to manage server. A Flutter project which provide charts to display Linux[](../../issues/43) server status and tools to manage server.
<br> <br>
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>. Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
</p> </p>

View File

@@ -1,8 +1,7 @@
class CpuStatus { class CpuStatus {
List<OneTimeCpuStatus> _pre; List<OneTimeCpuStatus> _pre;
List<OneTimeCpuStatus> _now; List<OneTimeCpuStatus> _now;
String temp; CpuStatus(this._pre, this._now);
CpuStatus(this._pre, this._now, this.temp);
double usedPercent({int coreIdx = 0}) { double usedPercent({int coreIdx = 0}) {
if (_now.length != _pre.length) return 0; if (_now.length != _pre.length) return 0;
@@ -12,10 +11,9 @@ class CpuStatus {
return used.isNaN ? 0 : 100 - used * 100; return used.isNaN ? 0 : 100 - used * 100;
} }
void update(List<OneTimeCpuStatus> newStatus, String newTemp) { void update(List<OneTimeCpuStatus> newStatus) {
_pre = _now; _pre = _now;
_now = newStatus; _now = newStatus;
temp = newTemp;
} }
int get coresCount => _now.length; int get coresCount => _now.length;
@@ -96,27 +94,3 @@ List<OneTimeCpuStatus> parseCPU(String raw) {
} }
return cpus; return cpus;
} }
final cpuTempReg = RegExp(r'(x86_pkg_temp|cpu_thermal)');
String parseCPUTemp(String type, String value) {
const noMatch = "/sys/class/thermal/thermal_zone*/type";
// Not support to get CPU temperature
if (type.contains(noMatch) || value.isEmpty || type.isEmpty) {
return '';
}
final split = type.split('\n');
// if no match, use idx 0
int idx = 0;
for (var item in split) {
if (item.contains(cpuTempReg)) {
break;
}
idx++;
}
final valueSplited = value.split('\n');
if (idx >= valueSplited.length) return '';
final temp = int.tryParse(valueSplited[idx].trim());
if (temp == null) return '';
return '${(temp / 1000).toStringAsFixed(1)}°C';
}

View File

@@ -1,3 +1,5 @@
import 'package:toolbox/data/model/server/temp.dart';
import 'cpu_status.dart'; import 'cpu_status.dart';
import 'disk_info.dart'; import 'disk_info.dart';
import 'memory.dart'; import 'memory.dart';
@@ -13,6 +15,7 @@ class ServerStatus {
List<DiskInfo> disk; List<DiskInfo> disk;
ConnStatus tcp; ConnStatus tcp;
NetSpeed netSpeed; NetSpeed netSpeed;
Temperatures temps;
String? failedInfo; String? failedInfo;
ServerStatus({ ServerStatus({
@@ -24,6 +27,7 @@ class ServerStatus {
required this.tcp, required this.tcp,
required this.netSpeed, required this.netSpeed,
required this.swap, required this.swap,
required this.temps,
this.failedInfo, this.failedInfo,
}); });
} }

View File

@@ -24,6 +24,7 @@ extension _SegmentsExt on List<String> {
Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async { Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
final net = parseNetSpeed(req.segments.at(CmdType.net)); final net = parseNetSpeed(req.segments.at(CmdType.net));
req.ss.netSpeed.update(net); req.ss.netSpeed.update(net);
final sys = _parseSysVer( final sys = _parseSysVer(
req.segments.at(CmdType.sys), req.segments.at(CmdType.sys),
req.segments.at(CmdType.host), req.segments.at(CmdType.host),
@@ -32,22 +33,29 @@ Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
if (sys != null) { if (sys != null) {
req.ss.sysVer = sys; req.ss.sysVer = sys;
} }
final cpus = parseCPU(req.segments.at(CmdType.cpu)); final cpus = parseCPU(req.segments.at(CmdType.cpu));
final cpuTemp = parseCPUTemp( req.ss.cpu.update(cpus);
req.ss.temps.parse(
req.segments.at(CmdType.tempType), req.segments.at(CmdType.tempType),
req.segments.at(CmdType.tempVal), req.segments.at(CmdType.tempVal),
); );
req.ss.cpu.update(cpus, cpuTemp);
final tcp = parseConn(req.segments.at(CmdType.conn)); final tcp = parseConn(req.segments.at(CmdType.conn));
if (tcp != null) { if (tcp != null) {
req.ss.tcp = tcp; req.ss.tcp = tcp;
} }
req.ss.disk = parseDisk(req.segments.at(CmdType.disk)); req.ss.disk = parseDisk(req.segments.at(CmdType.disk));
req.ss.mem = parseMem(req.segments.at(CmdType.mem)); req.ss.mem = parseMem(req.segments.at(CmdType.mem));
final uptime = _parseUpTime(req.segments.at(CmdType.uptime)); final uptime = _parseUpTime(req.segments.at(CmdType.uptime));
if (uptime != null) { if (uptime != null) {
req.ss.uptime = uptime; req.ss.uptime = uptime;
} }
req.ss.swap = parseSwap(req.segments.at(CmdType.mem)); req.ss.swap = parseSwap(req.segments.at(CmdType.mem));
return req.ss; return req.ss;
} }

View File

@@ -0,0 +1,61 @@
class Temperatures {
final Map<String, double> _map = {};
Temperatures();
void parse(String type, String value) {
const noMatch = "/sys/class/thermal/thermal_zone*/type";
// Not support to get CPU temperature
if (type.contains(noMatch) || value.isEmpty || type.isEmpty) {
return;
}
final typeSplited = type.split('\n');
final valueSplited = value.split('\n');
if (typeSplited.length != valueSplited.length) {
return;
}
for (var i = 0; i < typeSplited.length; i++) {
final t = typeSplited[i];
final v = valueSplited[i];
if (t.isEmpty || v.isEmpty) {
continue;
}
final name = t.split('/').last;
final temp = double.tryParse(v);
if (temp == null) {
continue;
}
_map[name] = temp / 1000;
}
}
void add(String name, double value) {
_map[name] = value;
}
double? get(String name) {
return _map[name];
}
Iterable<String> get devices {
return _map.keys;
}
bool get isEmpty {
return _map.isEmpty;
}
double? get first {
if (_map.isEmpty) {
return null;
}
for (final key in _map.keys) {
if (cpuTempReg.hasMatch(key)) {
return _map[key];
}
}
return _map.values.first;
}
}
final cpuTempReg = RegExp(r'(x86_pkg_temp|cpu_thermal)');

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:toolbox/data/res/ui.dart';
import '../../data/res/misc.dart'; import '../../data/res/misc.dart';
@@ -51,7 +52,7 @@ class DebugProvider extends ChangeNotifier {
void _addWidget(Widget widget) { void _addWidget(Widget widget) {
widgets.add(widget); widgets.add(widget);
widgets.add(const SizedBox(height: 13)); widgets.add(height13);
if (widgets.length > maxDebugLogLines) { if (widgets.length > maxDebugLogLines) {
widgets.removeRange(0, widgets.length - maxDebugLogLines); widgets.removeRange(0, widgets.length - maxDebugLogLines);
} }

View File

@@ -1,3 +1,5 @@
import 'package:toolbox/data/model/server/temp.dart';
import '../model/server/cpu_status.dart'; import '../model/server/cpu_status.dart';
import '../model/server/disk_info.dart'; import '../model/server/disk_info.dart';
import '../model/server/memory.dart'; import '../model/server/memory.dart';
@@ -24,7 +26,6 @@ OneTimeCpuStatus get _initOneTimeCpuStatus => OneTimeCpuStatus(
CpuStatus get initCpuStatus => CpuStatus( CpuStatus get initCpuStatus => CpuStatus(
[_initOneTimeCpuStatus], [_initOneTimeCpuStatus],
[_initOneTimeCpuStatus], [_initOneTimeCpuStatus],
'',
); );
NetSpeedPart get _initNetSpeedPart => NetSpeedPart( NetSpeedPart get _initNetSpeedPart => NetSpeedPart(
'', '',
@@ -50,4 +51,5 @@ ServerStatus get initStatus => ServerStatus(
tcp: ConnStatus(0, 0, 0, 0), tcp: ConnStatus(0, 0, 0, 0),
netSpeed: initNetSpeed, netSpeed: initNetSpeed,
swap: _initSwap, swap: _initSwap,
temps: Temperatures(),
); );

View File

@@ -22,6 +22,7 @@ const roundRectCardPadding = EdgeInsets.symmetric(horizontal: 17, vertical: 13);
/// SizedBox /// SizedBox
const placeholder = SizedBox.shrink();
const height13 = SizedBox(height: 13); const height13 = SizedBox(height: 13);
const width13 = SizedBox(width: 13); const width13 = SizedBox(width: 13);
const width7 = SizedBox(width: 7); const width7 = SizedBox(width: 7);

View File

@@ -57,6 +57,7 @@
"fileNotExist": "{file} existiert nicht", "fileNotExist": "{file} existiert nicht",
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}", "fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
"files": "Dateien", "files": "Dateien",
"font": "Schriftarten",
"fontSize": "Schriftgröße", "fontSize": "Schriftgröße",
"foundNUpdate": "Update {count} gefunden", "foundNUpdate": "Update {count} gefunden",
"getPushTokenFailed": "Push-Token kann nicht abgerufen werden", "getPushTokenFailed": "Push-Token kann nicht abgerufen werden",
@@ -151,7 +152,7 @@
"sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.", "sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.",
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?", "sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?", "sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
"termTheme": "Farbschema des Terminals", "theme": "Themen",
"themeMode": "Thememodus", "themeMode": "Thememodus",
"times": "x", "times": "x",
"ttl": "ttl", "ttl": "ttl",

View File

@@ -57,6 +57,7 @@
"fileNotExist": "{file} not exist", "fileNotExist": "{file} not exist",
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}", "fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
"files": "Files", "files": "Files",
"font": "Font",
"fontSize": "Font size", "fontSize": "Font size",
"foundNUpdate": "Found {count} update", "foundNUpdate": "Found {count} update",
"getPushTokenFailed": "Can't fetch push token", "getPushTokenFailed": "Can't fetch push token",
@@ -151,7 +152,7 @@
"sureDirEmpty": "Make sure dir is empty.", "sureDirEmpty": "Make sure dir is empty.",
"sureNoPwd": "Are you sure to use no password?", "sureNoPwd": "Are you sure to use no password?",
"sureToDeleteServer": "Are you sure to delete server [{server}]?", "sureToDeleteServer": "Are you sure to delete server [{server}]?",
"termTheme": "Terminal theme", "theme": "Theme",
"themeMode": "Theme mode", "themeMode": "Theme mode",
"times": "Times", "times": "Times",
"ttl": "ttl", "ttl": "ttl",

View File

@@ -57,6 +57,7 @@
"fileNotExist": "{file} 不存在", "fileNotExist": "{file} 不存在",
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}", "fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
"files": "文件", "files": "文件",
"font": "字体",
"fontSize": "字体大小", "fontSize": "字体大小",
"foundNUpdate": "找到 {count} 个更新", "foundNUpdate": "找到 {count} 个更新",
"getPushTokenFailed": "未能获取到推送token", "getPushTokenFailed": "未能获取到推送token",
@@ -151,7 +152,7 @@
"sureDirEmpty": "请确保文件夹为空", "sureDirEmpty": "请确保文件夹为空",
"sureNoPwd": "确认使用无密码?", "sureNoPwd": "确认使用无密码?",
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?", "sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
"termTheme": "终端主题", "theme": "主题",
"themeMode": "主题模式", "themeMode": "主题模式",
"times": "次", "times": "次",
"ttl": "缓存时间", "ttl": "缓存时间",

View File

@@ -55,9 +55,7 @@ class BackupPage extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
const SizedBox( const SizedBox(height: 107),
height: 107,
),
_buildCard(s.restore, Icons.download, media, () async { _buildCard(s.restore, Icons.download, media, () async {
final path = await pickOneFile(); final path = await pickOneFile();
if (path == null) { if (path == null) {
@@ -72,12 +70,12 @@ class BackupPage extends StatelessWidget {
final text = await file.readAsString(); final text = await file.readAsString();
_import(text, context, s); _import(text, context, s);
}), }),
const SizedBox(height: 17), height13,
const SizedBox( const SizedBox(
width: 37, width: 37,
child: Divider(), child: Divider(),
), ),
const SizedBox(height: 17), height13,
_buildCard( _buildCard(
s.backup, s.backup,
Icons.file_upload, Icons.file_upload,
@@ -122,7 +120,7 @@ class BackupPage extends StatelessWidget {
icon, icon,
color: textColor, color: textColor,
), ),
const SizedBox(width: 7), width7,
Text(text, style: TextStyle(color: textColor)), Text(text, style: TextStyle(color: textColor)),
], ],
), ),

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:toolbox/data/res/ui.dart';
import '../../core/utils/ui.dart'; import '../../core/utils/ui.dart';
import '../widget/input_field.dart'; import '../widget/input_field.dart';
@@ -48,7 +49,7 @@ class _ConvertPageState extends State<ConvertPage>
controller: ScrollController(), controller: ScrollController(),
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 13), height13,
_buildInputTop(), _buildInputTop(),
_buildMiddleBtns(), _buildMiddleBtns(),
_buildResult(), _buildResult(),

View File

@@ -277,7 +277,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
Widget _buildImages() { Widget _buildImages() {
if (_docker.images == null) { if (_docker.images == null) {
return const SizedBox(); return placeholder;
} }
final items = _docker.images! final items = _docker.images!
.map( .map(
@@ -336,7 +336,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
} }
Widget _buildLoading() { Widget _buildLoading() {
if (!_docker.isBusy) return const SizedBox(); if (!_docker.isBusy) return placeholder;
return Padding( return Padding(
padding: const EdgeInsets.all(17), padding: const EdgeInsets.all(17),
child: Column( child: Column(
@@ -344,7 +344,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
const Center( const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
const SizedBox(height: 17), height13,
Text(_docker.runLog ?? '...'), Text(_docker.runLog ?? '...'),
], ],
), ),
@@ -353,7 +353,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
Widget _buildEditHost() { Widget _buildEditHost() {
if (_docker.items!.isNotEmpty || _docker.images!.isNotEmpty) { if (_docker.items!.isNotEmpty || _docker.images!.isNotEmpty) {
return const SizedBox(); return placeholder;
} }
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0), padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),

View File

@@ -53,7 +53,7 @@ class _PingPageState extends State<PingPage>
padding: const EdgeInsets.symmetric(horizontal: 7), padding: const EdgeInsets.symmetric(horizontal: 7),
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 13), height13,
Input( Input(
controller: _textEditingController, controller: _textEditingController,
hint: s.inputDomainHere, hint: s.inputDomainHere,

View File

@@ -133,7 +133,7 @@ class _PkgManagePageState extends State<PkgManagePage>
Widget _buildFAB(PkgProvider pkg) { Widget _buildFAB(PkgProvider pkg) {
if (pkg.isBusy || (pkg.upgradeable?.isEmpty ?? true)) { if (pkg.isBusy || (pkg.upgradeable?.isEmpty ?? true)) {
return const SizedBox(); return placeholder;
} }
return FloatingActionButton( return FloatingActionButton(
onPressed: () { onPressed: () {

View File

@@ -42,13 +42,13 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
late PrivateKeyProvider _provider; late PrivateKeyProvider _provider;
late S _s; late S _s;
Widget _loading = const SizedBox(); Widget _loading = placeholder;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_provider = locator<PrivateKeyProvider>(); _provider = locator<PrivateKeyProvider>();
_loading = const SizedBox(); _loading = placeholder;
} }
@override @override
@@ -79,7 +79,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
context.pop(); context.pop();
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
: const SizedBox() : placeholder
], ],
); );
} }
@@ -112,7 +112,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
rethrow; rethrow;
} finally { } finally {
setState(() { setState(() {
_loading = const SizedBox(); _loading = placeholder;
}); });
} }
if (widget.info != null) { if (widget.info != null) {

View File

@@ -60,47 +60,38 @@ class _ServerDetailPageState extends State<ServerDetailPage>
body: ListView( body: ListView(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
children: [ children: [
...(_buildLinuxIcon(si.status.sysVer) ?? []), _buildLinuxIcon(si.status.sysVer),
_buildUpTimeAndSys(si.status), _buildUpTimeAndSys(si.status),
_buildCPUView(si.status), _buildCPUView(si.status),
_buildMemView(si.status), _buildMemView(si.status),
_buildSwapView(si.status), _buildSwapView(si.status),
_buildDiskView(si.status), _buildDiskView(si.status),
_buildNetView(si.status.netSpeed), _buildNetView(si.status.netSpeed),
// avoid the hieght of navigation bar _buildTemperature(si.status),
// height of navigation bar
SizedBox(height: _media.padding.bottom), SizedBox(height: _media.padding.bottom),
], ],
), ),
); );
} }
List<Widget>? _buildLinuxIcon(String sysVer) { Widget _buildLinuxIcon(String sysVer) {
if (!_showDistLogo) return null; if (!_showDistLogo) return placeholder;
final iconPath = sysVer.dist?.iconPath; final iconPath = sysVer.dist?.iconPath;
if (iconPath == null) return null; if (iconPath == null) return placeholder;
return [ return ConstrainedBox(
SizedBox(height: _media.size.height * 0.03), constraints: BoxConstraints(
ConstrainedBox( maxHeight: _media.size.height * 0.13,
constraints: BoxConstraints( maxWidth: _media.size.width * 0.6,
maxHeight: _media.size.height * 0.13,
maxWidth: _media.size.width * 0.6,
),
child: Image.asset(
iconPath,
fit: BoxFit.contain,
),
), ),
SizedBox(height: _media.size.height * 0.03), child: Image.asset(
]; iconPath,
fit: BoxFit.contain,
),
);
} }
Widget _buildCPUView(ServerStatus ss) { Widget _buildCPUView(ServerStatus ss) {
final tempWidget = ss.cpu.temp.isEmpty
? const SizedBox()
: Text(
ss.cpu.temp,
style: textSize13Grey,
);
return RoundRectCard( return RoundRectCard(
Padding( Padding(
padding: roundRectCardPadding, padding: roundRectCardPadding,
@@ -108,16 +99,10 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Text(
children: [ '${ss.cpu.usedPercent(coreIdx: 0).toInt()}%',
Text( style: textSize27,
'${ss.cpu.usedPercent(coreIdx: 0).toInt()}%', textScaleFactor: 1.0,
style: textSize27,
textScaleFactor: 1.0,
),
width7,
tempWidget
],
), ),
Row( Row(
children: [ children: [
@@ -244,7 +229,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
} }
Widget _buildSwapView(ServerStatus ss) { Widget _buildSwapView(ServerStatus ss) {
if (ss.swap.total == 0) return const SizedBox(); if (ss.swap.total == 0) return placeholder;
final used = ss.swap.usedPercent * 100; final used = ss.swap.usedPercent * 100;
final cached = ss.swap.cached / ss.swap.total * 100; final cached = ss.swap.cached / ss.swap.total * 100;
return RoundRectCard( return RoundRectCard(
@@ -396,5 +381,40 @@ class _ServerDetailPageState extends State<ServerDetailPage>
); );
} }
Widget _buildTemperature(ServerStatus ss) {
if (ss.temps.isEmpty) {
return placeholder;
}
final List<Widget> children = [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.device_hub, size: 17),
Icon(Icons.arrow_downward, size: 17),
],
),
const Padding(padding: EdgeInsets.symmetric(vertical: 3), child: Divider(height: 7),),
];
children.addAll(ss.temps.devices.map((key) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
key,
style: textSize11,
textScaleFactor: 1.0,
),
Text(
'${ss.temps.get(key)}°C',
style: textSize11,
textScaleFactor: 1.0,
),
],
)));
return RoundRectCard(Padding(
padding: roundRectCardPadding,
child: Column(children: children),
));
}
static const _ignorePath = ['udev', 'tmpfs', 'devtmpfs']; static const _ignorePath = ['udev', 'tmpfs', 'devtmpfs'];
} }

View File

@@ -96,7 +96,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
return AppBar( return AppBar(
title: Text(_s.edit, style: textSize18), title: Text(_s.edit, style: textSize18),
actions: [ actions: [
widget.spi != null ? delBtn : const SizedBox(), widget.spi != null ? delBtn : placeholder,
], ],
); );
} }
@@ -165,8 +165,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
hint: _s.pwd, hint: _s.pwd,
onSubmitted: (_) => {}, onSubmitted: (_) => {},
) )
: const SizedBox(), : placeholder,
usePublicKey ? _buildKeyAuth() : const SizedBox() usePublicKey ? _buildKeyAuth() : placeholder
], ],
), ),
); );

View File

@@ -108,20 +108,20 @@ class _ServerPageState extends State<ServerPage>
Widget _buildEachServerCard(Server? si) { Widget _buildEachServerCard(Server? si) {
if (si == null) { if (si == null) {
return const SizedBox(); return placeholder;
} }
return RoundRectCard( return GestureDetector(
GestureDetector( key: Key(si.spi.id),
child: Padding( onTap: () => AppRoute(
ServerDetailPage(si.spi.id),
'server detail page',
).go(context),
child: RoundRectCard(
Padding(
padding: const EdgeInsets.all(13), padding: const EdgeInsets.all(13),
child: _buildRealServerCard(si.status, si.state, si.spi), child: _buildRealServerCard(si.status, si.state, si.spi),
), ),
onTap: () => AppRoute(
ServerDetailPage(si.spi.id),
'server detail page',
).go(context),
), ),
key: Key(si.spi.id),
); );
} }
@@ -136,9 +136,7 @@ class _ServerPageState extends State<ServerPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildServerCardTitle(ss, cs, spi), _buildServerCardTitle(ss, cs, spi),
const SizedBox( height13,
height: 17,
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
@@ -149,7 +147,7 @@ class _ServerPageState extends State<ServerPage>
'Total:\n${rootDisk.size}', 'Used:\n${rootDisk.usedPercent}%') 'Total:\n${rootDisk.size}', 'Used:\n${rootDisk.usedPercent}%')
], ],
), ),
const SizedBox(height: 13), height13,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
@@ -169,9 +167,6 @@ class _ServerPageState extends State<ServerPage>
ServerState cs, ServerState cs,
ServerPrivateInfo spi, ServerPrivateInfo spi,
) { ) {
final topRightStr =
getTopRightStr(cs, ss.cpu.temp, ss.uptime, ss.failedInfo);
final hasError = cs == ServerState.failed && ss.failedInfo != null;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 7), padding: const EdgeInsets.symmetric(horizontal: 7),
child: Row( child: Row(
@@ -194,32 +189,8 @@ class _ServerPageState extends State<ServerPage>
), ),
Row( Row(
children: [ children: [
hasError _buildTopRightText(ss, cs),
? GestureDetector( width7,
onTap: () => showRoundDialog(
context: context,
title: Text(_s.error),
child: Text(ss.failedInfo ?? _s.unknownError),
actions: [
TextButton(
onPressed: () => copy2Clipboard(
ss.failedInfo ?? _s.unknownError),
child: Text(_s.copy),
)
],
),
child: Text(
_s.viewErr,
style: textSize12Grey,
textScaleFactor: 1.0,
),
)
: Text(
topRightStr,
style: textSize12Grey,
textScaleFactor: 1.0,
),
const SizedBox(width: 9),
_buildSSHBtn(spi), _buildSSHBtn(spi),
_buildMoreBtn(spi), _buildMoreBtn(spi),
], ],
@@ -229,6 +200,41 @@ class _ServerPageState extends State<ServerPage>
); );
} }
Widget _buildTopRightText(ServerStatus ss, ServerState cs) {
final topRightStr = getTopRightStr(
cs,
ss.temps.first,
ss.uptime,
ss.failedInfo,
);
final hasError = cs == ServerState.failed && ss.failedInfo != null;
return hasError
? GestureDetector(
onTap: () => showRoundDialog(
context: context,
title: Text(_s.error),
child: Text(ss.failedInfo ?? _s.unknownError),
actions: [
TextButton(
onPressed: () =>
copy2Clipboard(ss.failedInfo ?? _s.unknownError),
child: Text(_s.copy),
)
],
),
child: Text(
_s.viewErr,
style: textSize12Grey,
textScaleFactor: 1.0,
),
)
: Text(
topRightStr,
style: textSize12Grey,
textScaleFactor: 1.0,
);
}
Widget _buildSSHBtn(ServerPrivateInfo spi) { Widget _buildSSHBtn(ServerPrivateInfo spi) {
return GestureDetector( return GestureDetector(
child: const Icon( child: const Icon(
@@ -311,24 +317,18 @@ class _ServerPageState extends State<ServerPage>
} }
String getTopRightStr( String getTopRightStr(
ServerState cs, String temp, String upTime, String? failedInfo) { ServerState cs,
double? temp,
String upTime,
String? failedInfo,
) {
switch (cs) { switch (cs) {
case ServerState.disconnected: case ServerState.disconnected:
return _s.disconnected; return _s.disconnected;
case ServerState.connected: case ServerState.connected:
if (temp == '') { final tempStr = temp == null ? '' : '${temp.toStringAsFixed(1)}°C';
if (upTime == '') { final items = [tempStr, upTime];
return _s.serverTabLoading; return items.where((element) => element.isNotEmpty).join(' | ');
} else {
return upTime;
}
} else {
if (upTime == '') {
return temp;
} else {
return '$temp | $upTime';
}
}
case ServerState.connecting: case ServerState.connecting:
return _s.serverTabConnecting; return _s.serverTabConnecting;
case ServerState.failed: case ServerState.failed:

View File

@@ -317,7 +317,7 @@ class _SettingPageState extends State<SettingPage> {
.toList(); .toList();
return ListTile( return ListTile(
title: Text( title: Text(
_s.termTheme, _s.theme,
), ),
onTap: () { onTap: () {
termThemeKey.currentState?.showButtonMenu(); termThemeKey.currentState?.showButtonMenu();
@@ -465,7 +465,7 @@ class _SettingPageState extends State<SettingPage> {
Widget _buildFont() { Widget _buildFont() {
final fontName = getFileName(_setting.fontPath.fetch()); final fontName = getFileName(_setting.fontPath.fetch());
return ListTile( return ListTile(
title: Text(_s.choose), title: Text(_s.font),
trailing: Text( trailing: Text(
fontName ?? _s.notSelected, fontName ?? _s.notSelected,
style: textSize15, style: textSize15,

View File

@@ -266,7 +266,7 @@ class _SFTPPageState extends State<SFTPPage> {
title: Text(_s.download), title: Text(_s.download),
onTap: () => download(context, file), onTap: () => download(context, file),
) )
: const SizedBox() : placeholder
], ],
), ),
actions: [ actions: [

View File

@@ -54,7 +54,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
}, },
tooltip: _s.delete, tooltip: _s.delete,
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
: const SizedBox() : placeholder
], ],
), ),
body: _buildBody(), body: _buildBody(),