opt.: server detail page columns

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-06-04 17:29:03 +08:00
parent 1084c49a5f
commit 7127c960f7
32 changed files with 301 additions and 413 deletions

View File

@@ -19,7 +19,7 @@ extension on _ServerDetailPageState {
);
}
void _nTapGpuProcessItem(NvidiaSmiMemProcess process) {
void _onTapGpuProcessItem(NvidiaSmiMemProcess process) {
context.showRoundDialog(
title: '${process.pid}',
titleMaxLines: 1,
@@ -30,30 +30,18 @@ extension on _ServerDetailPageState {
UIs.height13,
Text('Memory: ${process.memory} MiB'),
UIs.height13,
Text('Process: ${process.name}')
Text('Process: ${process.name}'),
],
),
actions: [
TextButton(
onPressed: () => context.pop(),
child: Text(libL10n.close),
)
],
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),
),
],
child: SingleChildScrollView(child: Text(cmd.value, style: UIs.text13Grey)),
actions: [TextButton(onPressed: () => context.pop(), child: Text(libL10n.close))],
);
}
@@ -76,13 +64,18 @@ extension on _ServerDetailPageState {
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,
;
recv;
bool get isDevice => this == _NetSortType.device;
bool get isIn => this == _NetSortType.recv;
@@ -117,68 +110,55 @@ Widget _buildLineChart(
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();
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);
},
),
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,
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(
borderData: FlBorderData(show: false),
minY: -1,
maxY: 101,
lineBarsData: spots
.map(
(e) => LineChartBarData(
spots: e,
isCurved: curve,
barWidth: 2,
@@ -186,7 +166,9 @@ Widget _buildLineChart(
color: UIs.primaryColor,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
))
.toList(),
));
),
)
.toList(),
),
);
}

View File

@@ -15,13 +15,13 @@ import 'package:server_box/data/model/server/dist.dart';
import 'package:server_box/data/model/server/net_speed.dart';
import 'package:server_box/data/model/server/nvdia.dart';
import 'package:server_box/data/model/server/sensors.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/pve.dart';
import 'package:server_box/view/page/server/edit.dart';
import 'package:server_box/view/widget/server_func_btns.dart';
import 'package:server_box/data/model/server/server.dart';
part 'misc.dart';
@@ -32,32 +32,26 @@ class ServerDetailPage extends StatefulWidget {
@override
State<ServerDetailPage> createState() => _ServerDetailPageState();
static const route = AppRouteArg(
page: ServerDetailPage.new,
path: '/servers/detail',
);
static const route = AppRouteArg(page: ServerDetailPage.new, path: '/servers/detail');
}
class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerProviderStateMixin {
late final _cardBuildMap = Map.fromIterables(
ServerDetailCards.names,
[
_buildAbout,
_buildCPUView,
_buildMemView,
_buildSwapView,
_buildGpuView,
_buildDiskView,
_buildNetView,
_buildSensors,
_buildTemperature,
_buildBatteries,
_buildPve,
_buildCustomCmd,
],
);
late final _cardBuildMap = Map.fromIterables(ServerDetailCards.names, [
_buildAbout,
_buildCPUView,
_buildMemView,
_buildSwapView,
_buildGpuView,
_buildDiskView,
_buildNetView,
_buildSensors,
_buildTemperature,
_buildBatteries,
_buildPve,
_buildCustomCmd,
]);
late MediaQueryData _media;
late Size _size;
final List<String> _cardsOrder = [];
final _settings = Stores.setting;
@@ -74,7 +68,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
@override
void didChangeDependencies() {
super.didChangeDependencies();
_media = MediaQuery.of(context);
_size = MediaQuery.sizeOf(context);
}
@override
@@ -100,14 +94,11 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
Widget _buildMainPage(Server si) {
final buildFuncs = !Stores.setting.moveServerFuncs.fetch();
final logo = _buildLogo(si);
final children = [
logo,
if (buildFuncs) ServerFuncBtns(spi: si.spi),
];
final children = <Widget>[if (logo != null) logo, if (buildFuncs) ServerFuncBtns(spi: si.spi)];
for (final card in _cardsOrder) {
final buildFunc = _cardBuildMap[card];
if (buildFunc != null) {
children.add(buildFunc(si));
final child = _cardBuildMap[card]?.call(si);
if (child != null) {
children.add(child);
}
}
@@ -121,36 +112,26 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
return CustomAppBar(
title: Text(
si.spi.name,
style: TextStyle(
fontSize: 20,
color: context.isDark ? Colors.white : Colors.black,
),
style: TextStyle(fontSize: 20, color: context.isDark ? Colors.white : Colors.black),
),
actions: [
QrShareBtn(
data: si.spi.toJsonString(),
tip: si.spi.name,
tip2: '${l10n.server} ~ ServerBox',
),
QrShareBtn(data: si.spi.toJsonString(), tip: si.spi.name, tip2: '${l10n.server} ~ ServerBox'),
IconButton(
icon: const Icon(Icons.edit),
onPressed: () async {
final delete = await ServerEditPage.route.go(
context,
args: SpiRequiredArgs(si.spi),
);
final delete = await ServerEditPage.route.go(context, args: SpiRequiredArgs(si.spi));
if (delete == true) {
context.pop();
}
},
)
),
],
);
}
Widget _buildLogo(Server si) {
Widget? _buildLogo(Server si) {
var logoUrl = si.spi.custom?.logoUrl ?? _settings.serverLogoUrl.fetch().selfNotEmptyOrNull;
if (logoUrl == null) return UIs.placeholder;
if (logoUrl == null) return null;
final dist = si.status.more[StatusCmdType.sys]?.dist;
if (dist != null) {
@@ -160,25 +141,23 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
return Padding(
padding: const EdgeInsets.symmetric(vertical: 13),
child: ExtendedImage.network(
logoUrl,
cache: true,
height: _media.size.height * 0.2,
child: LayoutBuilder(
builder: (_, cons) {
if (logoUrl == null) return UIs.placeholder;
return ExtendedImage.network(logoUrl, cache: true, height: cons.maxWidth * 0.2);
},
),
);
}
Widget _buildAbout(Server si) {
Widget? _buildAbout(Server si) {
final ss = si.status;
return ExpandTile(
key: ValueKey(ss.more.hashCode), // Use hashCode to avoid perf issue
leading: const Icon(MingCute.information_fill, size: 20),
initiallyExpanded: _getInitExpand(ss.more.entries.length),
title: Text(libL10n.about),
childrenPadding: const EdgeInsets.symmetric(
horizontal: 17,
vertical: 11,
),
childrenPadding: const EdgeInsets.symmetric(horizontal: 17, vertical: 11),
children: ss.more.entries
.map(
(e) => Padding(
@@ -186,16 +165,8 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
e.key.i18n,
style: UIs.text13,
overflow: TextOverflow.ellipsis,
),
Text(
e.value,
style: UIs.text13Grey,
overflow: TextOverflow.ellipsis,
),
Text(e.key.i18n, style: UIs.text13, overflow: TextOverflow.ellipsis),
Text(e.value, style: UIs.text13Grey, overflow: TextOverflow.ellipsis),
],
),
),
@@ -204,13 +175,13 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
).cardx;
}
Widget _buildCPUView(Server si) {
Widget? _buildCPUView(Server si) {
final ss = si.status;
final percent = ss.cpu.usedPercent(coreIdx: 0).toInt();
final details = [
_buildDetailPercent(ss.cpu.user, 'user'),
UIs.width13,
_buildDetailPercent(ss.cpu.idle, 'idle')
_buildDetailPercent(ss.cpu.idle, 'idle'),
];
if (ss.system == SystemType.linux) {
details.addAll([
@@ -223,62 +194,41 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
final List<Widget> children = Stores.setting.cpuViewAsProgress.fetch()
? _buildCPUProgress(ss.cpu)
: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
child: SizedBox(
height: 137,
width: _media.size.width - 26 - 34,
child: _buildLineChart(
ss.cpu.spots,
//ss.cpu.rangeX,
tooltipPrefix: 'CPU',
),
),
),
];
: [_buildCPUChart(ss)];
if (ss.cpu.brand.isNotEmpty) {
children.add(Column(
children: ss.cpu.brand.entries.map(_buildCpuModelItem).toList(),
).paddingOnly(top: 13));
children.add(
Column(children: ss.cpu.brand.entries.map(_buildCpuModelItem).toList()).paddingOnly(top: 13),
);
}
return ExpandTile(
title: Align(
alignment: Alignment.centerLeft,
child: _buildAnimatedText(
ValueKey(percent),
'$percent%',
UIs.text27,
),
child: _buildAnimatedText(ValueKey(percent), '$percent%', UIs.text27),
),
childrenPadding: const EdgeInsets.symmetric(vertical: 13),
initiallyExpanded: _getInitExpand(1),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: details,
),
trailing: Row(mainAxisSize: MainAxisSize.min, children: details),
children: children,
).cardx;
}
Widget _buildCpuModelItem(MapEntry<String, int> e) {
final name =
e.key.replaceFirst('Intel(R)', '').replaceFirst('AMD', '').replaceFirst('with Radeon Graphics', '');
final name = e.key
.replaceFirst('Intel(R)', '')
.replaceFirst('AMD', '')
.replaceFirst('with Radeon Graphics', '');
final child = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: _media.size.width * .7,
),
child: Text(
name,
style: UIs.text13,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
LayoutBuilder(
builder: (_, cons) {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: cons.maxWidth * .7),
child: Text(name, style: UIs.text13, overflow: TextOverflow.ellipsis, maxLines: 1),
);
},
),
Text('x ${e.value}', style: UIs.text13Grey, overflow: TextOverflow.clip),
],
@@ -292,16 +242,8 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${percent.toStringAsFixed(1)}%',
style: UIs.text12,
textScaler: _textFactor,
),
Text(
timeType,
style: UIs.text12Grey,
textScaler: _textFactor,
),
Text('${percent.toStringAsFixed(1)}%', style: UIs.text12, textScaler: _textFactor),
Text(timeType, style: UIs.text12Grey, textScaler: _textFactor),
],
);
}
@@ -341,9 +283,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
children.add(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: Row(
children: rowChildren.joinWith(UIs.width7).toList(),
),
child: Row(children: rowChildren.joinWith(UIs.width7).toList()),
),
);
}
@@ -362,6 +302,25 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
return children;
}
Widget _buildCPUChart(ServerStatus ss) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
child: LayoutBuilder(
builder: (_, cons) {
return SizedBox(
height: 137,
width: cons.maxWidth,
child: _buildLineChart(
ss.cpu.spots,
//ss.cpu.rangeX,
tooltipPrefix: 'CPU',
),
);
},
),
);
}
Widget _buildProgress(double percent) {
if (percent > 100) percent = 100;
final percentWithinOne = percent / 100;
@@ -373,7 +332,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
);
}
Widget _buildMemView(Server si) {
Widget? _buildMemView(Server si) {
final ss = si.status;
final free = ss.mem.free / ss.mem.total * 100;
final avail = ss.mem.availPercent * 100;
@@ -384,10 +343,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
children: [
_buildAnimatedText(ValueKey(usedStr), '$usedStr%', UIs.text27),
UIs.width7,
Text(
'of ${(ss.mem.total * 1024).bytes2Str}',
style: UIs.text13Grey,
)
Text('of ${(ss.mem.total * 1024).bytes2Str}', style: UIs.text13Grey),
],
);
@@ -411,15 +367,16 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
],
),
UIs.height13,
_buildProgress(used)
_buildProgress(used),
],
),
).cardx;
}
Widget _buildSwapView(Server si) {
Widget? _buildSwapView(Server si) {
final ss = si.status;
if (ss.swap.total == 0) return UIs.placeholder;
if (ss.swap.total == 0) return null;
final used = ss.swap.usedPercent * 100;
final cached = ss.swap.cached / ss.swap.total * 100;
@@ -427,10 +384,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
children: [
Text('${used.toStringAsFixed(0)}%', style: UIs.text27),
UIs.width7,
Text(
'of ${(ss.swap.total * 1024).bytes2Str} ',
style: UIs.text13Grey,
)
Text('of ${(ss.swap.total * 1024).bytes2Str} ', style: UIs.text13Grey),
],
);
@@ -442,21 +396,19 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
percentW,
_buildDetailPercent(cached, 'cached'),
],
children: [percentW, _buildDetailPercent(cached, 'cached')],
),
UIs.height13,
_buildProgress(used)
_buildProgress(used),
],
),
).cardx;
}
Widget _buildGpuView(Server si) {
Widget? _buildGpuView(Server si) {
final ss = si.status;
if (ss.nvidia == null || ss.nvidia?.isEmpty == true) return UIs.placeholder;
if (ss.nvidia == null || ss.nvidia?.isEmpty == true) return null;
final children = ss.nvidia?.map((e) => _buildGpuItem(e)).toList() ?? [];
return ExpandTile(
title: const Text('GPU'),
@@ -486,10 +438,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () => _onTapGpuItem(item),
icon: const Icon(Icons.info_outline, size: 17),
),
IconButton(onPressed: () => _onTapGpuItem(item), icon: const Icon(Icons.info_outline, size: 17)),
],
),
);
@@ -510,13 +459,13 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
textScaler: _textFactor,
),
trailing: InkWell(
onTap: () => _nTapGpuProcessItem(process),
onTap: () => _onTapGpuProcessItem(process),
child: const Icon(Icons.info_outline, size: 17),
),
);
}
Widget _buildDiskView(Server si) {
Widget? _buildDiskView(Server si) {
final ss = si.status;
final children = <Widget>[];
@@ -526,7 +475,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
children.add(_buildDiskItemWithHierarchy(disk, ss, 0));
}
if (children.isEmpty) return UIs.placeholder;
if (children.isEmpty) return null;
return ExpandTile(
title: Text(l10n.disk),
@@ -583,11 +532,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
style: UIs.text12,
textScaler: _textFactor,
),
Text(
text,
style: UIs.text12Grey,
textScaler: _textFactor,
)
Text(text, style: UIs.text12Grey, textScaler: _textFactor),
],
),
),
@@ -604,21 +549,21 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
backgroundColor: UIs.halfAlpha,
color: UIs.primaryColor,
),
Text('${disk.usedPercent}%', style: UIs.text12Grey)
Text('${disk.usedPercent}%', style: UIs.text12Grey),
],
),
)
),
],
),
);
}
Widget _buildNetView(Server si) {
Widget? _buildNetView(Server si) {
final ss = si.status;
final ns = ss.netSpeed;
final children = <Widget>[];
final devices = ns.devices;
if (devices.isEmpty) return UIs.placeholder;
if (devices.isEmpty) return null;
devices.sort(_netSortType.value.getSortFunc(ns));
children.addAll(devices.map((e) => _buildNetSpeedItem(ns, e)));
@@ -634,18 +579,12 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
onTap: () => _netSortType.value = val.next,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 377),
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: child,
),
transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child),
child: Row(
children: [
const Icon(Icons.sort, size: 17),
UIs.width7,
Text(
val.name,
style: UIs.text13Grey,
),
Text(val.name, style: UIs.text13Grey),
],
),
),
@@ -680,7 +619,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
'${ns.sizeIn(device: device)} | ${ns.sizeOut(device: device)}',
style: UIs.text12Grey,
textScaler: _textFactor,
)
),
],
),
SizedBox(
@@ -690,17 +629,16 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
textAlign: TextAlign.end,
style: UIs.text13Grey,
),
)
),
],
),
);
}
Widget _buildTemperature(Server si) {
Widget? _buildTemperature(Server si) {
final ss = si.status;
if (ss.temps.isEmpty) {
return UIs.placeholder;
}
if (ss.temps.isEmpty) return null;
return CardX(
child: ExpandTile(
title: Text(l10n.temperature),
@@ -729,11 +667,10 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
);
}
Widget _buildBatteries(Server si) {
Widget? _buildBatteries(Server si) {
final ss = si.status;
if (ss.batteries.isEmpty) {
return UIs.placeholder;
}
if (ss.batteries.isEmpty) return null;
return CardX(
child: ExpandTile(
title: Text(l10n.battery),
@@ -756,22 +693,16 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${battery.name}', style: UIs.text15),
Text(
'${battery.status.name} - ${battery.cycle}',
style: UIs.text13Grey,
),
Text('${battery.status.name} - ${battery.cycle}', style: UIs.text13Grey),
],
),
Text(
'${battery.percent?.toStringAsFixed(0)}%',
style: UIs.text13Grey,
),
Text('${battery.percent?.toStringAsFixed(0)}%', style: UIs.text13Grey),
],
),
);
}
Widget _buildSensors(Server si) {
Widget? _buildSensors(Server si) {
final ss = si.status;
if (ss.sensors.isEmpty) return UIs.placeholder;
return CardX(
@@ -824,9 +755,9 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
);
}
Widget _buildPve(Server si) {
Widget? _buildPve(Server si) {
final addr = si.spi.custom?.pveAddr;
if (addr == null || addr.isEmpty) return UIs.placeholder;
if (addr == null || addr.isEmpty) return null;
return CardX(
child: ListTile(
title: const Text('PVE'),
@@ -837,9 +768,9 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
);
}
Widget _buildCustomCmd(Server si) {
Widget? _buildCustomCmd(Server si) {
final ss = si.status;
if (ss.customCmds.isEmpty) return UIs.placeholder;
if (ss.customCmds.isEmpty) return null;
return CardX(
child: ExpandTile(
leading: const Icon(MingCute.command_line, size: 17),
@@ -860,11 +791,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
if (!cmd.value.contains('\n')) return null;
return GestureDetector(
onTap: () => _onTapCustomItem(cmd),
child: const Icon(
Icons.info_outline,
size: 17,
color: Colors.grey,
),
child: const Icon(Icons.info_outline, size: 17, color: Colors.grey),
);
},
),
@@ -874,21 +801,8 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
Widget _buildAnimatedText(Key key, String text, TextStyle style) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 277),
child: Text(
key: key,
text,
style: style,
textScaler: _textFactor,
),
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: child,
),
child: Text(key: key, text, style: style, textScaler: _textFactor),
transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child),
);
}
bool _getInitExpand(int len, [int? max]) {
if (!_collapse) return true;
return len > 0 && len <= (max ?? 3);
}
}

View File

@@ -8,11 +8,10 @@ import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/server/custom.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:server_box/data/provider/private_key.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/store/server.dart';
import 'package:server_box/view/page/private_key/edit.dart';

View File

@@ -10,26 +10,25 @@ import 'package:responsive_framework/responsive_framework.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/try_limiter.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/server/detail/view.dart';
import 'package:server_box/view/page/server/edit.dart';
import 'package:server_box/view/page/setting/entry.dart';
import 'package:server_box/view/widget/percent_circle.dart';
import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/view/widget/server_func_btns.dart';
part 'top_bar.dart';
part 'card_stat.dart';
part 'utils.dart';
part 'content.dart';
part 'landscape.dart';
part 'top_bar.dart';
part 'utils.dart';
class ServerPage extends StatefulWidget {
const ServerPage({super.key});
@@ -141,10 +140,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
});
}
Widget _buildBodySmall({
required List<String> filtered,
EdgeInsets? padding = const EdgeInsets.fromLTRB(0, 0, 5, 7),
}) {
Widget _buildBodySmall({required List<String> filtered}) {
if (filtered.isEmpty) {
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
}
@@ -153,6 +149,9 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
builder: (_, cons) {
// Calculate number of columns based on available width
final columnsCount = math.max(1, (cons.maxWidth / UIs.columnWidth).floor());
final padding = columnsCount > 1
? const EdgeInsets.fromLTRB(0, 0, 5, 7)
: const EdgeInsets.fromLTRB(7, 0, 7, 7);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -176,10 +175,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
final vnode = ServerProvider.pick(id: serversInThisColumn[index]);
if (vnode == null) return UIs.placeholder;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 0),
child: vnode.listenVal(_buildEachServerCard),
);
return vnode.listenVal(_buildEachServerCard);
},
),
);
@@ -190,9 +186,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
}
Widget _buildEachServerCard(Server? srv) {
if (srv == null) {
return UIs.placeholder;
}
if (srv == null) return UIs.placeholder;
return CardX(
key: Key(srv.spi.id + _tag.value),