diff --git a/README.md b/README.md index 680a102b..f446b9ea 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel ## 🔖 Feature -- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Pkg & Process`... +- `Status chart` (CPU, Sensors, GPU...), `SSH` Term, `SFTP`, `Docker & Process`... - Platform specific: `Bio auth`、`Msg push`、`Home widget`、`watchOS App`... - English, 简体中文; Deutsch [@its-tom](https://github.com/its-tom), 繁體中文 [@kalashnikov](https://github.com/kalashnikov), Indonesian [@azkadev](https://github.com/azkadev), Français [@FrancXPT](https://github.com/FrancXPT), Dutch [@QazCetelic](https://github.com/QazCetelic); Español, Русский язык, Português, 日本語 (Generated by GPT) diff --git a/README_zh.md b/README_zh.md index a15d53db..55430729 100644 --- a/README_zh.md +++ b/README_zh.md @@ -28,7 +28,7 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel ## 🔖 特点 -- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 包 & 进程` 管理器... +- `状态图表`(CPU、传感器、GPU 等), `SSH` 终端, `SFTP`, `Docker & 进程` 管理... - 特殊支持:`生物认证`、`推送`、`桌面小部件`、`watchOS App`、`跟随系统颜色`... - 本地化 - English, 简体中文 diff --git a/lib/data/model/app/menu/server_func.dart b/lib/data/model/app/menu/server_func.dart index 7857e440..c97628e9 100644 --- a/lib/data/model/app/menu/server_func.dart +++ b/lib/data/model/app/menu/server_func.dart @@ -15,8 +15,8 @@ enum ServerFuncBtn { container, @HiveField(3) process, - @HiveField(4) - pkg, + //@HiveField(4) + //pkg, @HiveField(5) snippet, @HiveField(6) @@ -30,14 +30,14 @@ enum ServerFuncBtn { sftp, container, process, - pkg, + //pkg, snippet, ].map((e) => e.index).toList(); IconData get icon => switch (this) { sftp => Icons.insert_drive_file, snippet => Icons.code, - pkg => Icons.system_security_update, + //pkg => Icons.system_security_update, container => FontAwesome.docker_brand, process => Icons.list_alt_outlined, terminal => Icons.terminal, @@ -47,7 +47,7 @@ enum ServerFuncBtn { String get toStr => switch (this) { sftp => 'SFTP', snippet => l10n.snippet, - pkg => l10n.pkg, + //pkg => l10n.pkg, container => l10n.container, process => l10n.process, terminal => l10n.terminal, diff --git a/lib/data/model/app/menu/server_func.g.dart b/lib/data/model/app/menu/server_func.g.dart index cf83cef3..e679540c 100644 --- a/lib/data/model/app/menu/server_func.g.dart +++ b/lib/data/model/app/menu/server_func.g.dart @@ -21,8 +21,6 @@ class ServerFuncBtnAdapter extends TypeAdapter { return ServerFuncBtn.container; case 3: return ServerFuncBtn.process; - case 4: - return ServerFuncBtn.pkg; case 5: return ServerFuncBtn.snippet; case 6: @@ -47,9 +45,6 @@ class ServerFuncBtnAdapter extends TypeAdapter { case ServerFuncBtn.process: writer.writeByte(3); break; - case ServerFuncBtn.pkg: - writer.writeByte(4); - break; case ServerFuncBtn.snippet: writer.writeByte(5); break; diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index adf32958..d633aa54 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -170,15 +170,6 @@ class _ServerEditPageState extends State with AfterLayoutMixin { hint: 'root', suggestion: false, ), - Input( - controller: _altUrlController, - type: TextInputType.url, - node: _alterUrlFocus, - label: l10n.fallbackSshDest, - icon: MingCute.link_line, - hint: 'user@ip:port', - suggestion: false, - ), TagEditor( tags: _tags, onChanged: (p0) => _tags = p0, @@ -328,19 +319,16 @@ class _ServerEditPageState extends State with AfterLayoutMixin { return ExpandTile( title: Text(l10n.more), children: [ - const Text('Logo', style: UIs.text13Grey), UIs.height7, Input( controller: _logoUrlCtrl, type: TextInputType.url, icon: Icons.image, - label: 'URL', + label: 'Logo URL', hint: 'https://example.com/logo.png', suggestion: false, ), - UIs.height7, - Text(l10n.envVars, style: UIs.text13Grey), - UIs.height7, + _buildAltUrl(), _buildEnvs(), UIs.height7, ..._buildPVEs(), @@ -363,6 +351,18 @@ class _ServerEditPageState extends State with AfterLayoutMixin { ); } + Widget _buildAltUrl() { + return Input( + controller: _altUrlController, + type: TextInputType.url, + node: _alterUrlFocus, + label: l10n.fallbackSshDest, + icon: MingCute.link_line, + hint: 'user@ip:port', + suggestion: false, + ); + } + List _buildPVEs() { const addr = 'https://127.0.0.1:8006'; return [ diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index e654b975..b3cdbb36 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -27,6 +27,9 @@ class ServerPage extends StatefulWidget { State createState() => _ServerPageState(); } +const _cardPad = 74.0; +const _cardPadSingle = 13.0; + class _ServerPageState extends State with AutomaticKeepAliveClientMixin, AfterLayoutMixin { late MediaQueryData _media; @@ -97,7 +100,7 @@ class _ServerPageState extends State ), floatingActionButton: AutoHide( key: _autoHideKey, - direction: AxisDirection.down, + direction: AxisDirection.right, offset: 75, controller: _scrollController, child: FloatingActionButton( @@ -289,8 +292,12 @@ class _ServerPageState extends State } }, child: Padding( - padding: - const EdgeInsets.only(left: 13, right: 3, top: 13, bottom: 13), + padding: const EdgeInsets.only( + left: _cardPadSingle, + right: 3, + top: _cardPadSingle, + bottom: _cardPadSingle, + ), child: _buildRealServerCard(srv), ), ), @@ -300,7 +307,7 @@ class _ServerPageState extends State /// The child's width mat not equal to 1/4 of the screen width, /// so we need to wrap it with a SizedBox. Widget _wrapWithSizedbox(Widget child, [bool circle = false]) { - var width = (_media.size.width - 74) / (circle ? 4 : 4.3); + var width = (_media.size.width - _cardPad) / (circle ? 4 : 4.3); if (_useDoubleColumn) width /= 2; return SizedBox( width: width, @@ -341,66 +348,72 @@ class _ServerPageState extends State } List _buildFlippedCard(Server srv) { + final children = [ + IconTextBtn( + onPressed: () => _askFor( + func: () async { + if (Stores.setting.showSuspendTip.fetch()) { + await context.showRoundDialog( + title: l10n.attention, + child: Text(l10n.suspendTip), + ); + Stores.setting.showSuspendTip.put(false); + } + srv.client?.execWithPwd( + ShellFunc.suspend.exec, + context: context, + id: srv.id, + ); + }, + typ: l10n.suspend, + name: srv.spi.name, + ), + icon: Icons.stop, + text: l10n.suspend, + ), + IconTextBtn( + onPressed: () => _askFor( + func: () => srv.client?.execWithPwd( + ShellFunc.shutdown.exec, + context: context, + id: srv.id, + ), + typ: l10n.shutdown, + name: srv.spi.name, + ), + icon: Icons.power_off, + text: l10n.shutdown, + ), + IconTextBtn( + onPressed: () => _askFor( + func: () => srv.client?.execWithPwd( + ShellFunc.reboot.exec, + context: context, + id: srv.id, + ), + typ: l10n.reboot, + name: srv.spi.name, + ), + icon: Icons.restart_alt, + text: l10n.reboot, + ), + IconTextBtn( + onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context), + icon: Icons.edit, + text: l10n.edit, + ) + ]; + + final width = (_media.size.width - _cardPad) / children.length; return [ UIs.height13, Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - IconTextBtn( - onPressed: () => _askFor( - func: () async { - if (Stores.setting.showSuspendTip.fetch()) { - await context.showRoundDialog( - title: l10n.attention, - child: Text(l10n.suspendTip), - ); - Stores.setting.showSuspendTip.put(false); - } - srv.client?.execWithPwd( - ShellFunc.suspend.exec, - context: context, - id: srv.id, - ); - }, - typ: l10n.suspend, - name: srv.spi.name, - ), - icon: Icons.stop, - text: l10n.suspend, - ), - IconTextBtn( - onPressed: () => _askFor( - func: () => srv.client?.execWithPwd( - ShellFunc.shutdown.exec, - context: context, - id: srv.id, - ), - typ: l10n.shutdown, - name: srv.spi.name, - ), - icon: Icons.power_off, - text: l10n.shutdown, - ), - IconTextBtn( - onPressed: () => _askFor( - func: () => srv.client?.execWithPwd( - ShellFunc.reboot.exec, - context: context, - id: srv.id, - ), - typ: l10n.reboot, - name: srv.spi.name, - ), - icon: Icons.restart_alt, - text: l10n.reboot, - ), - IconTextBtn( - onPressed: () => AppRoutes.serverEdit(spi: srv.spi).go(context), - icon: Icons.edit, - text: l10n.edit, - ) - ], - ) + children: children.map((e) { + if (width == 0) return e; + return SizedBox(width: width, child: e); + }).toList(), + ), ]; } diff --git a/lib/view/page/setting/seq/srv_func_seq.dart b/lib/view/page/setting/seq/srv_func_seq.dart index 3dbe92a6..4c79c993 100644 --- a/lib/view/page/setting/seq/srv_func_seq.dart +++ b/lib/view/page/setting/seq/srv_func_seq.dart @@ -45,7 +45,7 @@ class _ServerDetailOrderPageState extends State { text: TextSpan( children: [ WidgetSpan(child: Icon(funcBtn.icon)), - const WidgetSpan(child: UIs.width7), + const WidgetSpan(child: UIs.width13), TextSpan(text: funcBtn.toStr, style: UIs.textGrey), ], ), diff --git a/lib/view/widget/server_func_btns.dart b/lib/view/widget/server_func_btns.dart index ea01bab9..0daf96fe 100644 --- a/lib/view/widget/server_func_btns.dart +++ b/lib/view/widget/server_func_btns.dart @@ -3,19 +3,14 @@ import 'dart:io'; import 'package:fl_lib/fl_lib.dart'; import 'package:flutter/material.dart'; import 'package:server_box/core/extension/context/locale.dart'; -import 'package:server_box/core/extension/ssh_client.dart'; import 'package:server_box/data/model/app/menu/base.dart'; import 'package:server_box/data/model/app/menu/server_func.dart'; -import 'package:server_box/data/model/app/shell_func.dart'; -import 'package:server_box/data/model/pkg/manager.dart'; -import 'package:server_box/data/model/server/dist.dart'; import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/res/provider.dart'; import 'package:server_box/data/res/store.dart'; import '../../core/route.dart'; import '../../core/utils/server.dart'; -import '../../data/model/pkg/upgrade_info.dart'; import '../../data/model/server/server_private_info.dart'; class ServerFuncBtnsTopRight extends StatelessWidget { @@ -98,9 +93,9 @@ void _onTapMoreBtns( BuildContext context, ) async { switch (value) { - case ServerFuncBtn.pkg: - _onPkg(context, spi); - break; + // case ServerFuncBtn.pkg: + // _onPkg(context, spi); + // break; case ServerFuncBtn.sftp: AppRoutes.sftp(spi: spi).checkGo( context: context, @@ -225,86 +220,86 @@ bool _checkClient(BuildContext context, String id) { return true; } -Future _onPkg(BuildContext context, ServerPrivateInfo spi) async { - final server = spi.server; - final client = server?.client; - if (server == null || client == null) { - context.showSnackBar(l10n.noClient); - return; - } - final sys = server.status.more[StatusCmdType.sys]; - if (sys == null) { - context.showSnackBar(l10n.noResult); - return; - } +// Future _onPkg(BuildContext context, ServerPrivateInfo spi) async { +// final server = spi.server; +// final client = server?.client; +// if (server == null || client == null) { +// context.showSnackBar(l10n.noClient); +// return; +// } +// final sys = server.status.more[StatusCmdType.sys]; +// if (sys == null) { +// context.showSnackBar(l10n.noResult); +// return; +// } - final pkg = PkgManager.fromDist(sys.dist); - if (pkg == null) { - context.showSnackBar('Unsupported dist: $sys'); - return; - } +// final pkg = PkgManager.fromDist(sys.dist); +// if (pkg == null) { +// context.showSnackBar('Unsupported dist: $sys'); +// return; +// } - // Update pkg list - final suc = await context.showLoadingDialog( - fn: () async { - final updateCmd = pkg.update; - if (updateCmd != null) { - await client.execWithPwd( - updateCmd, - context: context, - id: spi.id, - ); - } - }, - barrierDismiss: true, - ); - if (suc != true) return; +// // Update pkg list +// final suc = await context.showLoadingDialog( +// fn: () async { +// final updateCmd = pkg.update; +// if (updateCmd != null) { +// await client.execWithPwd( +// updateCmd, +// context: context, +// id: spi.id, +// ); +// } +// }, +// barrierDismiss: true, +// ); +// if (suc != true) return; - final listCmd = pkg.listUpdate; - if (listCmd == null) { - context.showSnackBar('Unsupported dist: $sys'); - return; - } +// final listCmd = pkg.listUpdate; +// if (listCmd == null) { +// context.showSnackBar('Unsupported dist: $sys'); +// return; +// } - // Get upgrade list - final result = await context.showLoadingDialog( - fn: () => client.run(listCmd).string, - ); - if (result == null || result.isEmpty) { - context.showSnackBar(l10n.noResult); - return; - } +// // Get upgrade list +// final result = await context.showLoadingDialog( +// fn: () => client.run(listCmd).string, +// ); +// if (result == null || result.isEmpty) { +// context.showSnackBar(l10n.noResult); +// return; +// } - final list = pkg.updateListRemoveUnused(result.split('\n')); - final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList(); - if (upgradeable.isEmpty) { - context.showSnackBar(l10n.noUpdateAvailable); - return; - } - final args = upgradeable.map((e) => e.package).join(' '); - final isSU = server.spi.user == 'root'; - final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}'; +// final list = pkg.updateListRemoveUnused(result.split('\n')); +// final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList(); +// if (upgradeable.isEmpty) { +// context.showSnackBar(l10n.noUpdateAvailable); +// return; +// } +// final args = upgradeable.map((e) => e.package).join(' '); +// final isSU = server.spi.user == 'root'; +// final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}'; - // Confirm upgrade - final gotoUpgrade = await context.showRoundDialog( - title: l10n.attention, - child: SingleChildScrollView( - child: Text( - '${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'), - ), - actions: [ - CountDownBtn( - onTap: () => context.pop(true), - text: l10n.update, - afterColor: Colors.red, - ), - ], - ); +// // Confirm upgrade +// final gotoUpgrade = await context.showRoundDialog( +// title: l10n.attention, +// child: SingleChildScrollView( +// child: Text( +// '${l10n.pkgUpgradeTip}\n${l10n.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'), +// ), +// actions: [ +// CountDownBtn( +// onTap: () => context.pop(true), +// text: l10n.update, +// afterColor: Colors.red, +// ), +// ], +// ); - if (gotoUpgrade != true) return; +// if (gotoUpgrade != true) return; - AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo( - context: context, - check: () => _checkClient(context, spi.id), - ); -} +// AppRoutes.ssh(spi: spi, initCmd: upgradeCmd).checkGo( +// context: context, +// check: () => _checkClient(context, spi.id), +// ); +// }