diff --git a/lib/core/route.dart b/lib/core/route.dart index f1cb5715..a9db118e 100644 --- a/lib/core/route.dart +++ b/lib/core/route.dart @@ -18,7 +18,6 @@ import '../view/page/convert.dart'; import '../view/page/debug.dart'; import '../view/page/editor.dart'; import '../view/page/full_screen.dart'; -import '../view/page/pkg.dart'; import '../view/page/process.dart'; import '../view/page/server/edit.dart'; import '../view/page/server/tab.dart'; @@ -188,10 +187,6 @@ class AppRoute { return AppRoute(PingPage(key: key), 'ping'); } - static AppRoute pkg({Key? key, required ServerPrivateInfo spi}) { - return AppRoute(PkgPage(key: key, spi: spi), 'pkg'); - } - static AppRoute process({Key? key, required ServerPrivateInfo spi}) { return AppRoute(ProcessPage(key: key, spi: spi), 'process'); } diff --git a/lib/data/model/pkg/manager.dart b/lib/data/model/pkg/manager.dart index 09c9d0d7..fb31edc4 100644 --- a/lib/data/model/pkg/manager.dart +++ b/lib/data/model/pkg/manager.dart @@ -92,28 +92,28 @@ enum PkgManager { list.removeWhere((element) => element.isEmpty); return list; } -} -PkgManager? fromDist(Dist? dist) { - switch (dist) { - case Dist.centos: - case Dist.rocky: - case Dist.fedora: - return PkgManager.yum; - case Dist.debian: - case Dist.ubuntu: - case Dist.kali: - case Dist.armbian: - return PkgManager.apt; - case Dist.opensuse: - return PkgManager.zypper; - case Dist.wrt: - return PkgManager.opkg; - case Dist.arch: - return PkgManager.pacman; - case Dist.alpine: - return PkgManager.apk; - default: - return null; + static PkgManager? fromDist(Dist? dist) { + switch (dist) { + case Dist.centos: + case Dist.rocky: + case Dist.fedora: + return PkgManager.yum; + case Dist.debian: + case Dist.ubuntu: + case Dist.kali: + case Dist.armbian: + return PkgManager.apt; + case Dist.opensuse: + return PkgManager.zypper; + case Dist.wrt: + return PkgManager.opkg; + case Dist.arch: + return PkgManager.pacman; + case Dist.alpine: + return PkgManager.apk; + default: + return null; + } } } diff --git a/lib/data/model/server/dist.dart b/lib/data/model/server/dist.dart index 06fd0357..89d72d0a 100644 --- a/lib/data/model/server/dist.dart +++ b/lib/data/model/server/dist.dart @@ -20,6 +20,17 @@ extension StringX on String { return dist; } } + for (final wrt in _wrts) { + if (lower.contains(wrt)) { + return Dist.wrt; + } + } return null; } } + +// Special rules + +const _wrts = [ + 'istoreos', +]; diff --git a/lib/data/provider/pkg.dart b/lib/data/provider/pkg.dart deleted file mode 100644 index 3e6382e7..00000000 --- a/lib/data/provider/pkg.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'dart:async'; - -import 'package:dartssh2/dartssh2.dart'; -import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; -import 'package:toolbox/core/extension/ssh_client.dart'; -import 'package:toolbox/core/extension/uint8list.dart'; -import 'package:toolbox/data/model/pkg/manager.dart'; -import 'package:toolbox/data/model/pkg/upgrade_info.dart'; -import 'package:toolbox/data/model/server/dist.dart'; - -class PkgProvider extends ChangeNotifier { - final logger = Logger('PKG'); - - SSHClient? client; - Dist? dist; - PkgManager? type; - Function()? onUpgrade; - Function()? onUpdate; - BuildContext? context; - - String? whoami; - List? upgradeable; - String? error; - String? upgradeLog; - String? updateLog; - String lastLog = ''; - - Future init( - SSHClient client, - Dist? dist, - Function() onUpgrade, - Function() onUpdate, - String user, - BuildContext context, - ) async { - this.client = client; - this.dist = dist; - this.onUpgrade = onUpgrade; - whoami = user; - - type = fromDist(dist); - if (type == null) { - error = 'Unsupported dist: $dist'; - } - } - - bool get isSU => whoami == 'root'; - - void clear() { - client = dist = updateLog = upgradeLog = - upgradeable = error = whoami = onUpdate = onUpgrade = context = null; - } - - Future refresh() async { - final result = await _update(); - try { - _parse(result); - } catch (e) { - error = '[Server Raw]:\n$result\n[App Error]:\n$e'; - rethrow; - } finally { - notifyListeners(); - } - } - - void _parse(String? raw) { - if (raw == null) return; - final list = type?.updateListRemoveUnused(raw.split('\n')); - upgradeable = list?.map((e) => UpgradePkgInfo(e, type)).toList(); - } - - Future _update() async { - final updateCmd = type?.update; - if (updateCmd != null) { - await client!.execWithPwd( - _wrap(updateCmd), - context: context, - onStdout: (data, sink) { - updateLog = (updateLog ?? '') + data; - if (onUpdate != null) onUpdate!(); - notifyListeners(); - }, - ); - } - final listCmd = type?.listUpdate; - if (listCmd == null) { - error = 'Unsupported dist: $dist'; - return null; - } - return await client?.run(_wrap(listCmd)).string; - } - - Future upgrade() async { - final upgradeCmd = - type?.upgrade(upgradeable?.map((e) => e.package).join(" ") ?? ''); - - if (upgradeCmd == null) { - error = 'Unsupported dist: $dist'; - return; - } - - await client!.execWithPwd( - _wrap(upgradeCmd), - context: context, - onStdout: (log, sink) { - if (lastLog == log.trim()) return; - upgradeLog = (upgradeLog ?? '') + log; - lastLog = log.trim(); - notifyListeners(); - if (onUpgrade != null) onUpgrade!(); - }, - ); - - upgradeLog = null; - refresh(); - } - - String _wrap(String cmd) => - 'export LANG=en_US.utf-8 && ${isSU ? "" : "sudo -S "}$cmd'; -} diff --git a/lib/locator.dart b/lib/locator.dart index ffaeaa1d..282f70cc 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -3,7 +3,6 @@ import 'package:get_it/get_it.dart'; import 'data/provider/app.dart'; import 'data/provider/debug.dart'; import 'data/provider/docker.dart'; -import 'data/provider/pkg.dart'; import 'data/provider/private_key.dart'; import 'data/provider/server.dart'; import 'data/provider/sftp.dart'; @@ -25,7 +24,6 @@ void _setupLocatorForServices() { void _setupLocatorForProviders() { locator.registerSingleton(AppProvider()); - locator.registerSingleton(PkgProvider()); locator.registerSingleton(DebugProvider()); locator.registerSingleton(DockerProvider()); locator.registerSingleton(ServerProvider()); diff --git a/lib/main.dart b/lib/main.dart index ce63b3ea..b33e626c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,6 @@ import 'data/model/ssh/virtual_key.dart'; import 'data/provider/app.dart'; import 'data/provider/debug.dart'; import 'data/provider/docker.dart'; -import 'data/provider/pkg.dart'; import 'data/provider/private_key.dart'; import 'data/provider/server.dart'; import 'data/provider/sftp.dart'; @@ -40,7 +39,6 @@ Future main() async { MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => locator()), - ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), diff --git a/lib/view/page/pkg.dart b/lib/view/page/pkg.dart deleted file mode 100644 index 5fe4a17e..00000000 --- a/lib/view/page/pkg.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:provider/provider.dart'; - -import '../../data/model/pkg/upgrade_info.dart'; -import '../../data/model/server/dist.dart'; -import '../../data/model/server/server_private_info.dart'; -import '../../data/provider/pkg.dart'; -import '../../data/provider/server.dart'; -import '../../data/res/ui.dart'; -import '../../locator.dart'; -import '../widget/custom_appbar.dart'; -import '../widget/round_rect_card.dart'; -import '../widget/two_line_text.dart'; - -class PkgPage extends StatefulWidget { - const PkgPage({Key? key, required this.spi}) : super(key: key); - - final ServerPrivateInfo spi; - - @override - _PkgManagePageState createState() => _PkgManagePageState(); -} - -class _PkgManagePageState extends State - with SingleTickerProviderStateMixin { - late MediaQueryData _media; - final _scrollController = ScrollController(); - final _scrollControllerUpdate = ScrollController(); - final _textController = TextEditingController(); - final _pkgProvider = locator(); - late S _s; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _media = MediaQuery.of(context); - _s = S.of(context)!; - } - - @override - void dispose() { - super.dispose(); - _pkgProvider.clear(); - _textController.dispose(); - _scrollController.dispose(); - _scrollControllerUpdate.dispose(); - } - - @override - void initState() { - super.initState(); - final si = locator().servers[widget.spi.id]; - - if (si == null) return; - _pkgProvider.init( - si.client!, - si.status.sysVer.dist, - () => - _scrollController.jumpTo(_scrollController.position.maxScrollExtent), - () => _scrollControllerUpdate - .jumpTo(_scrollController.position.maxScrollExtent), - widget.spi.user, - context, - ); - _pkgProvider.refresh(); - } - - @override - Widget build(BuildContext context) { - return Consumer(builder: (_, pkg, __) { - return Scaffold( - appBar: CustomAppBar( - centerTitle: true, - title: TwoLineText(up: _s.pkg, down: widget.spi.name), - ), - body: _buildBody(pkg), - floatingActionButton: _buildFAB(pkg), - ); - }); - } - - Widget? _buildFAB(PkgProvider pkg) { - if (pkg.upgradeable?.isEmpty ?? true) { - return null; - } - return FloatingActionButton( - onPressed: () { - pkg.upgrade(); - }, - child: const Icon(Icons.upgrade), - ); - } - - Widget _buildBody(PkgProvider pkg) { - if (pkg.error != null) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.error, - color: Colors.redAccent, - size: 37, - ), - const SizedBox( - height: 37, - ), - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: _media.size.height * 0.3, - minWidth: _media.size.width), - child: Padding( - padding: const EdgeInsets.all(17), - child: RoundRectCard( - SingleChildScrollView( - padding: const EdgeInsets.all(17), - child: Text( - pkg.error!, - ), - ), - ), - ), - ), - ], - ); - } - if (pkg.upgradeable == null) { - if (pkg.updateLog == null) { - return centerLoading; - } - return SizedBox( - height: _media.size.height - _media.padding.top - _media.padding.bottom, - child: ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: SingleChildScrollView( - padding: const EdgeInsets.all(18), - controller: _scrollControllerUpdate, - child: Text(pkg.updateLog!), - ), - ), - ); - } - - return ListView( - padding: const EdgeInsets.all(13), - children: _buildUpdatePanel(pkg).map((e) => RoundRectCard(e)).toList(), - ); - } - - List _buildUpdatePanel(PkgProvider apt) { - final children = []; - if (apt.upgradeable!.isEmpty) { - children.add(ListTile( - title: Text( - _s.noUpdateAvailable, - textAlign: TextAlign.center, - ), - subtitle: const Text('>_<', textAlign: TextAlign.center), - )); - return children; - } - - if (apt.upgradeLog == null) { - children.addAll( - apt.upgradeable?.map((e) => _buildUpdateItem(e, apt)).toList() ?? [], - ); - } else { - children.add(SingleChildScrollView( - padding: const EdgeInsets.all(18), - controller: _scrollController, - child: Text(apt.upgradeLog ?? ''), - )); - } - - return children; - } - - Widget _buildUpdateItem(UpgradePkgInfo info, PkgProvider apt) { - final t = () { - if (info.nowVersion.isNotEmpty && info.newVersion.isNotEmpty) { - return '${info.nowVersion} -> ${info.newVersion}'; - } - return info.newVersion; - }(); - return ListTile( - title: Text(info.package), - subtitle: Text(t, style: grey), - ); - } -} diff --git a/lib/view/widget/server_func_btns.dart b/lib/view/widget/server_func_btns.dart index e8ec0439..3b1663d1 100644 --- a/lib/view/widget/server_func_btns.dart +++ b/lib/view/widget/server_func_btns.dart @@ -2,6 +2,11 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/core/extension/context.dart'; +import 'package:toolbox/core/extension/ssh_client.dart'; +import 'package:toolbox/core/extension/uint8list.dart'; +import 'package:toolbox/data/model/pkg/manager.dart'; +import 'package:toolbox/data/model/server/dist.dart'; import '../../core/route.dart'; import '../../core/utils/misc.dart'; @@ -9,6 +14,7 @@ import '../../core/utils/platform.dart'; import '../../core/utils/server.dart'; import '../../core/utils/ui.dart'; import '../../data/model/app/menu.dart'; +import '../../data/model/pkg/upgrade_info.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/model/server/snippet.dart'; import '../../data/provider/server.dart'; @@ -86,10 +92,7 @@ void _onTapMoreBtns( ) async { switch (value) { case ServerTabMenuType.pkg: - AppRoute.pkg(spi: spi).checkGo( - context: context, - check: () => _checkClient(context, spi.id, s.waitConnection), - ); + _onPkg(context, s, spi); break; case ServerTabMenuType.sftp: AppRoute.sftp(spi: spi).checkGo( @@ -200,3 +203,70 @@ bool _checkClient(BuildContext context, String id, String msg) { } return true; } + +Future _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async { + final server = locator().servers[spi.id]; + if (server == null) { + showSnackBar(context, Text(s.noClient)); + return; + } + final sys = server.status.sysVer; + final pkg = PkgManager.fromDist(sys.dist); + + // Update pkg list + showLoadingDialog(context); + final updateCmd = pkg?.update; + if (updateCmd != null) { + await server.client!.execWithPwd( + updateCmd, + context: context, + ); + } + context.pop(); + + final listCmd = pkg?.listUpdate; + if (listCmd == null) { + showSnackBar(context, Text('Unsupported dist: $sys')); + return; + } + + // Get upgrade list + showLoadingDialog(context); + final result = await server.client?.run(listCmd).string; + context.pop(); + if (result == null) { + showSnackBar(context, Text(s.noResult)); + return; + } + final list = pkg?.updateListRemoveUnused(result.split('\n')); + final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList(); + if (upgradeable == null || upgradeable.isEmpty) { + showSnackBar(context, Text(s.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 showRoundDialog( + context: context, + title: Text(s.attention), + child: SingleChildScrollView( + child: Text('${s.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'), + ), + actions: [ + TextButton( + onPressed: () => context.pop(true), + child: Text(s.update), + ), + ], + ); + + if (gotoUpgrade != true) return; + + AppRoute.ssh(spi: spi, initCmd: upgradeCmd).checkGo( + context: context, + check: () => _checkClient(context, spi.id, s.waitConnection), + ); +}