mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
APT/Docker manage
- view apt update - view docker container
This commit is contained in:
@@ -354,7 +354,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -362,7 +362,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.97;
|
||||
MARKETING_VERSION = 1.0.101;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -484,7 +484,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -492,7 +492,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.97;
|
||||
MARKETING_VERSION = 1.0.101;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -508,7 +508,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 97;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -516,7 +516,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.97;
|
||||
MARKETING_VERSION = 1.0.101;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
import 'package:toolbox/data/model/distribution.dart';
|
||||
|
||||
extension StringX on String {
|
||||
int get i => int.parse(this);
|
||||
|
||||
Distribution get dist {
|
||||
final lower = toLowerCase();
|
||||
for (var dist in debianDistList) {
|
||||
if (lower.contains(dist)) {
|
||||
return Distribution.debian;
|
||||
}
|
||||
}
|
||||
for (var dist in rehlDistList) {
|
||||
if (lower.contains(dist)) {
|
||||
return Distribution.rehl;
|
||||
}
|
||||
}
|
||||
return Distribution.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
6
lib/core/extension/uint8list.dart
Normal file
6
lib/core/extension/uint8list.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
extension Uint8ListX on Future<Uint8List> {
|
||||
Future<String> get string async => utf8.decode(await this);
|
||||
}
|
||||
@@ -11,13 +11,13 @@ class MenuItem {
|
||||
}
|
||||
|
||||
class MenuItems {
|
||||
static const List<MenuItem> firstItems = [ssh, sftp, snippet, apt];
|
||||
static const List<MenuItem> firstItems = [sftp, snippet, apt, docker];
|
||||
static const List<MenuItem> secondItems = [edit];
|
||||
|
||||
static const ssh = MenuItem(text: 'SSH', icon: Icons.link);
|
||||
static const sftp = MenuItem(text: 'SFTP', icon: Icons.insert_drive_file);
|
||||
static const snippet = MenuItem(text: 'Snippet', icon: Icons.label);
|
||||
static const apt = MenuItem(text: 'Apt', icon: Icons.system_security_update);
|
||||
static const docker = MenuItem(text: 'Docker', icon: Icons.view_agenda);
|
||||
static const edit = MenuItem(text: 'Edit', icon: Icons.edit);
|
||||
|
||||
static Widget buildItem(MenuItem item) {
|
||||
|
||||
33
lib/data/model/apt/upgrade_pkg_info.dart
Normal file
33
lib/data/model/apt/upgrade_pkg_info.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:toolbox/data/model/distribution.dart';
|
||||
|
||||
class AptUpgradePkgInfo {
|
||||
final String _raw;
|
||||
final Distribution _dist;
|
||||
|
||||
late String package;
|
||||
late String nowVersion;
|
||||
late String newVersion;
|
||||
late String arch;
|
||||
|
||||
AptUpgradePkgInfo(this._raw, this._dist) {
|
||||
switch (_dist) {
|
||||
case Distribution.debian:
|
||||
case Distribution.unknown:
|
||||
_parseApt();
|
||||
break;
|
||||
case Distribution.rehl:
|
||||
_parseYum();
|
||||
}
|
||||
}
|
||||
|
||||
void _parseApt() {
|
||||
final split1 = _raw.split("/");
|
||||
package = split1[0];
|
||||
final split2 = split1[1].split(" ");
|
||||
newVersion = split2[1];
|
||||
arch = split2[2];
|
||||
nowVersion = split2[5].replaceFirst(']', '');
|
||||
}
|
||||
|
||||
void _parseYum() {}
|
||||
}
|
||||
21
lib/data/model/distribution.dart
Normal file
21
lib/data/model/distribution.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
enum Distribution {
|
||||
unknown,
|
||||
debian,
|
||||
rehl,
|
||||
}
|
||||
|
||||
const debianDistList = [
|
||||
'debian',
|
||||
'ubuntu',
|
||||
'linuxmint',
|
||||
'elementary',
|
||||
'raspbian'
|
||||
];
|
||||
const rehlDistList = [
|
||||
'redhat',
|
||||
'fedora',
|
||||
'centos',
|
||||
'scientificlinux',
|
||||
'rhel',
|
||||
'oraclelinux'
|
||||
];
|
||||
29
lib/data/model/docker/ps.dart
Normal file
29
lib/data/model/docker/ps.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
class DockerPsItem {
|
||||
late String containerId;
|
||||
late String image;
|
||||
late String command;
|
||||
late String created;
|
||||
late String status;
|
||||
late String ports;
|
||||
late String name;
|
||||
|
||||
DockerPsItem(this.containerId, this.image, this.command, this.created,
|
||||
this.status, this.ports, this.name);
|
||||
|
||||
DockerPsItem.fromRawString(String rawString) {
|
||||
final List<String> parts = rawString.split(' ');
|
||||
containerId = parts[0];
|
||||
image = parts[1];
|
||||
command = parts[2];
|
||||
created = parts[3];
|
||||
status = parts[4];
|
||||
ports = parts[5];
|
||||
if (running && parts.length == 9) {
|
||||
name = parts[8];
|
||||
} else {
|
||||
name = parts[6];
|
||||
}
|
||||
}
|
||||
|
||||
bool get running => status.contains('Up ');
|
||||
}
|
||||
@@ -13,6 +13,8 @@ class SFTPSideViewStatus {
|
||||
AbsolutePath? rightPath;
|
||||
SftpClient? leftClient;
|
||||
SftpClient? rightClient;
|
||||
bool isBusyLeft = false;
|
||||
bool isBusyRight = false;
|
||||
|
||||
SFTPSideViewStatus();
|
||||
|
||||
@@ -36,4 +38,8 @@ class SFTPSideViewStatus {
|
||||
SftpClient? client(bool left) => left ? leftClient : rightClient;
|
||||
void setClient(bool left, SftpClient? nClient) =>
|
||||
left ? leftClient = nClient : rightClient = nClient;
|
||||
|
||||
bool isBusy(bool left) => left ? isBusyLeft : isBusyRight;
|
||||
void setBusy(bool left, bool nBusy) =>
|
||||
left ? isBusyLeft = nBusy : isBusyRight = nBusy;
|
||||
}
|
||||
|
||||
48
lib/data/provider/apt.dart
Normal file
48
lib/data/provider/apt.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:toolbox/core/provider_base.dart';
|
||||
import 'package:toolbox/data/model/apt/upgrade_pkg_info.dart';
|
||||
import 'package:toolbox/data/model/distribution.dart';
|
||||
|
||||
class AptProvider extends BusyProvider {
|
||||
SSHClient? client;
|
||||
Distribution? dist;
|
||||
List<AptUpgradePkgInfo>? upgradeable;
|
||||
|
||||
AptProvider();
|
||||
|
||||
void init(SSHClient client, Distribution dist) {
|
||||
this.client = client;
|
||||
this.dist = dist;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
client = null;
|
||||
dist = null;
|
||||
upgradeable = null;
|
||||
}
|
||||
|
||||
bool get isReady {
|
||||
return upgradeable != null && !isBusy;
|
||||
}
|
||||
|
||||
Future<void> refreshInstalled() async {
|
||||
await update();
|
||||
final result = utf8.decode(await client!.run('apt list --upgradeable'));
|
||||
final list = result.split('\n').sublist(4);
|
||||
list.removeWhere((element) => element.isEmpty);
|
||||
upgradeable = list.map((e) => AptUpgradePkgInfo(e, dist!)).toList();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> update() async {
|
||||
await client!.run('apt update');
|
||||
}
|
||||
|
||||
// Future<void> upgrade() async {
|
||||
// setBusyState();
|
||||
// await client!.run('apt upgrade -y');
|
||||
// refreshInstalled();
|
||||
// }
|
||||
}
|
||||
63
lib/data/provider/docker.dart
Normal file
63
lib/data/provider/docker.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:toolbox/core/extension/uint8list.dart';
|
||||
import 'package:toolbox/core/provider_base.dart';
|
||||
import 'package:toolbox/data/model/docker/ps.dart';
|
||||
|
||||
class DockerProvider extends BusyProvider {
|
||||
SSHClient? client;
|
||||
List<DockerPsItem>? running;
|
||||
String? version;
|
||||
String? edition;
|
||||
String? error;
|
||||
|
||||
void init(SSHClient client) => this.client = client;
|
||||
|
||||
void clear() {
|
||||
client = null;
|
||||
error = null;
|
||||
running = null;
|
||||
version = null;
|
||||
edition = null;
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (client == null) {
|
||||
error = 'no client';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
final verRaw = await client!.run('docker version').string;
|
||||
final verSplit = verRaw.split('\n');
|
||||
if (verSplit.length < 3) {
|
||||
error = 'invalid version';
|
||||
notifyListeners();
|
||||
} else {
|
||||
version = verSplit[1].split(' ').last;
|
||||
edition = verSplit[0].split(': ')[1];
|
||||
}
|
||||
|
||||
final raw = await client!.run('docker ps -a').string;
|
||||
if (raw.contains('command not found')) {
|
||||
error = 'docker not found';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
final lines = raw.split('\n');
|
||||
lines.removeAt(0);
|
||||
lines.removeWhere((element) => element.isEmpty);
|
||||
running = lines.map((e) => DockerPsItem.fromRawString(e)).toList();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> stop(String id) async {
|
||||
if (client == null) {
|
||||
error = 'no client';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
final result = await client!.run('docker stop $id').string;
|
||||
await refresh();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 101;
|
||||
static const int build = 102;
|
||||
static const String engine =
|
||||
"Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 77d935af4d (3 months ago) • 2021-12-16 08:37:33 -0800\nEngine • revision 890a5fca2e\nTools • Dart 2.15.1\n";
|
||||
static const String buildAt = "2022-03-02 11:12:07.958841";
|
||||
static const int modifications = 4;
|
||||
"Flutter 2.10.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 7e9793dee1 (5 days ago) • 2022-03-02 11:23:12 -0600\nEngine • revision bd539267b4\nTools • Dart 2.16.1 • DevTools 2.9.2\n";
|
||||
static const String buildAt = "2022-03-07 19:19:07.115966";
|
||||
static const int modifications = 18;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/provider/apt.dart';
|
||||
import 'package:toolbox/data/provider/debug.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/private_key.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/snippet.dart';
|
||||
@@ -18,7 +20,9 @@ void setupLocatorForServices() {
|
||||
|
||||
void setupLocatorForProviders() {
|
||||
locator.registerSingleton(AppProvider());
|
||||
locator.registerSingleton(AptProvider());
|
||||
locator.registerSingleton(DebugProvider());
|
||||
locator.registerSingleton(DockerProvider());
|
||||
locator.registerSingleton(ServerProvider());
|
||||
locator.registerSingleton(SnippetProvider());
|
||||
locator.registerSingleton(PrivateKeyProvider());
|
||||
|
||||
@@ -7,7 +7,9 @@ import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/app.dart';
|
||||
import 'package:toolbox/core/analysis.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/provider/apt.dart';
|
||||
import 'package:toolbox/data/provider/debug.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/private_key.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/provider/snippet.dart';
|
||||
@@ -62,7 +64,9 @@ Future<void> main() async {
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => locator<AppProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<AptProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<DebugProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<DockerProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<ServerProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<SnippetProvider>()),
|
||||
ChangeNotifierProvider(create: (_) => locator<PrivateKeyProvider>()),
|
||||
|
||||
124
lib/view/page/apt.dart
Normal file
124
lib/view/page/apt.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/extension/stringx.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
import 'package:toolbox/data/model/apt/upgrade_pkg_info.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/provider/apt.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
class AptManagePage extends StatefulWidget {
|
||||
const AptManagePage(this.spi, {Key? key}) : super(key: key);
|
||||
|
||||
final ServerPrivateInfo spi;
|
||||
|
||||
@override
|
||||
_AptManagePageState createState() => _AptManagePageState();
|
||||
}
|
||||
|
||||
class _AptManagePageState extends State<AptManagePage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late MediaQueryData _media;
|
||||
final greyStyle = const TextStyle(color: Colors.grey);
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
locator<AptProvider>().clear();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final si = locator<ServerProvider>()
|
||||
.servers
|
||||
.firstWhere((e) => e.info == widget.spi);
|
||||
if (si.client == null) {
|
||||
showSnackBar(context, const Text('Plz wait for ssh connection'));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
locator<AptProvider>().init(si.client!, si.status.sysVer.dist);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Apt'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
locator<AptProvider>().refreshInstalled();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Consumer<AptProvider>(builder: (_, apt, __) {
|
||||
if (apt.upgradeable == null) {
|
||||
apt.refreshInstalled();
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(13),
|
||||
children: [_buildUpdatePanel(apt)],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUpdatePanel(AptProvider apt) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RoundRectCard(ExpansionTile(
|
||||
title: Text(!apt.isReady
|
||||
? 'Loading...'
|
||||
: 'Found ${apt.upgradeable!.length} update'),
|
||||
subtitle: !apt.isReady
|
||||
? null
|
||||
: Text(
|
||||
apt.upgradeable!.map((e) => e.package).join(', '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: greyStyle,
|
||||
),
|
||||
children: [
|
||||
// TextButton(
|
||||
// child: Text('Update all'),
|
||||
// onPressed: () {
|
||||
// apt.upgrade();
|
||||
// }),
|
||||
!apt.isReady
|
||||
? const SizedBox()
|
||||
: SizedBox(
|
||||
height: _media.size.height * 0.23,
|
||||
child: ListView(
|
||||
children: apt.upgradeable!
|
||||
.map((e) => _buildUpdateItem(e, apt))
|
||||
.toList()),
|
||||
)
|
||||
],
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUpdateItem(AptUpgradePkgInfo info, AptProvider apt) {
|
||||
return ListTile(
|
||||
title: Text(info.package),
|
||||
subtitle: Text(
|
||||
'${info.nowVersion} -> ${info.newVersion}',
|
||||
style: greyStyle,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
109
lib/view/page/docker.dart
Normal file
109
lib/view/page/docker.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
import 'package:toolbox/data/model/docker/ps.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/provider/docker.dart';
|
||||
import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
import 'package:toolbox/view/widget/center_loading.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
class DockerManagePage extends StatefulWidget {
|
||||
final ServerPrivateInfo spi;
|
||||
const DockerManagePage(this.spi, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DockerManagePage> createState() => _DockerManagePageState();
|
||||
}
|
||||
|
||||
class _DockerManagePageState extends State<DockerManagePage> {
|
||||
final _docker = locator<DockerProvider>();
|
||||
final greyTextStyle = const TextStyle(color: Colors.grey);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_docker.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final client = locator<ServerProvider>()
|
||||
.servers
|
||||
.firstWhere((element) => element.info == widget.spi)
|
||||
.client;
|
||||
if (client == null) {
|
||||
showSnackBar(context, const Text('No client found'));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
_docker.init(client);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Docker'),
|
||||
),
|
||||
body: _buildMain(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMain() {
|
||||
return Consumer<DockerProvider>(builder: (_, docker, __) {
|
||||
final running = docker.running;
|
||||
if (docker.error != null && running == null) {
|
||||
return Center(
|
||||
child: Text(docker.error!),
|
||||
);
|
||||
}
|
||||
if (running == null) {
|
||||
_docker.refresh();
|
||||
return centerLoading;
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(7),
|
||||
children:
|
||||
[_buildVersion(docker.edition ?? 'Unknown', docker.version ?? 'Unknown'), _buildPsItems(running)].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildVersion(String edition, String version) {
|
||||
return Padding(padding: EdgeInsets.all(13), child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(edition),
|
||||
Text(version)
|
||||
],
|
||||
),);
|
||||
}
|
||||
|
||||
Widget _buildPsItems(List<DockerPsItem> running) {
|
||||
return ExpansionTile(
|
||||
title: const Text('Container Status'),
|
||||
subtitle: Text(_buildSubtitle(running), style: greyTextStyle),
|
||||
children: running.map((item) {
|
||||
return ListTile(
|
||||
title: Text(item.image),
|
||||
subtitle: Text(item.status),
|
||||
trailing: IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(item.running ? Icons.stop : Icons.play_arrow)),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
String _buildSubtitle(List<DockerPsItem> running) {
|
||||
final runningCount = running.where((element) => element.running).length;
|
||||
final stoped = running.length - runningCount;
|
||||
if (stoped == 0) {
|
||||
return '$runningCount container running.';
|
||||
}
|
||||
return '$runningCount running, $stoped stoped.';
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
const Text('Public Key Auth'),
|
||||
const Text('Key Auth'),
|
||||
Switch(
|
||||
value: usePublicKey,
|
||||
onChanged: (val) => setState(() => usePublicKey = val)),
|
||||
|
||||
@@ -17,6 +17,8 @@ import 'package:toolbox/data/provider/server.dart';
|
||||
import 'package:toolbox/data/res/color.dart';
|
||||
import 'package:toolbox/data/store/setting.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
import 'package:toolbox/view/page/apt.dart';
|
||||
import 'package:toolbox/view/page/docker.dart';
|
||||
import 'package:toolbox/view/page/server/detail.dart';
|
||||
import 'package:toolbox/view/page/server/edit.dart';
|
||||
import 'package:toolbox/view/page/sftp.dart';
|
||||
@@ -240,9 +242,8 @@ class _ServerPageState extends State<ServerPage>
|
||||
onChanged: (value) {
|
||||
final item = value as MenuItem;
|
||||
switch (item) {
|
||||
case MenuItems.ssh:
|
||||
case MenuItems.apt:
|
||||
showSnackBar(context, const Text('Now is not supported'));
|
||||
AppRoute(AptManagePage(spi), 'apt manage page').go(context);
|
||||
break;
|
||||
case MenuItems.sftp:
|
||||
AppRoute(
|
||||
@@ -268,6 +269,9 @@ class _ServerPageState extends State<ServerPage>
|
||||
'Edit server info page')
|
||||
.go(context);
|
||||
break;
|
||||
case MenuItems.docker:
|
||||
AppRoute(DockerManagePage(spi), 'Docker manage page').go(context);
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemHeight: 37,
|
||||
|
||||
@@ -96,7 +96,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
|
||||
if (_status.files(left) == null) {
|
||||
_status.setPath(left, AbsolutePath('/'));
|
||||
listDir('/', left, client: client);
|
||||
listDir(left, path: '/', client: client);
|
||||
return centerCircleLoading;
|
||||
} else {
|
||||
return RefreshIndicator(
|
||||
@@ -114,21 +114,22 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file),
|
||||
title: Text(file.filename),
|
||||
subtitle:
|
||||
isDir ? null : Text((convertBytes(file.attr.size ?? 0))),
|
||||
isDir ? null : Text(convertBytes(file.attr.size ?? 0)),
|
||||
onTap: () {
|
||||
if (isDir) {
|
||||
_status.path(left)?.update(file.filename);
|
||||
listDir(_status.path(left)?.path ?? '/', left);
|
||||
listDir(left, path: _status.path(left)?.path);
|
||||
} else {
|
||||
onItemPress(context, left, file);
|
||||
}
|
||||
},
|
||||
onLongPress: () => onItemPress(context, left, file),
|
||||
);
|
||||
},
|
||||
),
|
||||
key: Key(_status.spi(left)!.name + _status.path(left)!.path),
|
||||
),
|
||||
onRefresh: () => listDir(_status.path(left)?.path ?? '/', left));
|
||||
onRefresh: () => listDir(left, path: _status.path(left)?.path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,21 +149,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
leading: const Icon(Icons.folder),
|
||||
title: const Text('Create Folder'),
|
||||
onTap: () => mkdir(context, left)),
|
||||
ListTile(
|
||||
leading: Icon(left ? Icons.arrow_forward : Icons.arrow_back),
|
||||
title: const Text('Copy'),
|
||||
onTap: () => copy(context, left, file),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: const Text('Rename'),
|
||||
onTap: () => rename(context, left, file),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_download),
|
||||
title: const Text('Download'),
|
||||
onTap: () => download(context, left, file),
|
||||
),
|
||||
],
|
||||
),
|
||||
[
|
||||
@@ -172,18 +163,19 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
]);
|
||||
}
|
||||
|
||||
void download(BuildContext context, bool left, SftpName file) {}
|
||||
|
||||
void copy(BuildContext context, bool left, SftpName file) {}
|
||||
|
||||
void delete(BuildContext context, bool left, SftpName file) {
|
||||
Navigator.of(context).pop();
|
||||
showRoundDialog(
|
||||
context, 'Confirm', Text('Are you sure to delete ${file.filename}?'), [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
_status.client(left)!.remove(file.filename);
|
||||
Navigator.of(context).pop();
|
||||
listDir(left);
|
||||
},
|
||||
child: const Text(
|
||||
'Delete',
|
||||
style: TextStyle(color: Colors.red),
|
||||
@@ -192,6 +184,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void mkdir(BuildContext context, bool left) {
|
||||
Navigator.of(context).pop();
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
@@ -219,6 +212,8 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
_status.client(left)!.mkdir(
|
||||
_status.path(left)!.path + '/' + textController.text);
|
||||
Navigator.of(context).pop();
|
||||
listDir(left);
|
||||
},
|
||||
child: const Text(
|
||||
'Create',
|
||||
@@ -228,10 +223,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
|
||||
void rename(BuildContext context, bool left, SftpName file) {
|
||||
Navigator.of(context).pop();
|
||||
final textController = TextEditingController();
|
||||
showRoundDialog(
|
||||
context,
|
||||
'Create Folder',
|
||||
'Rename',
|
||||
TextField(
|
||||
controller: textController,
|
||||
decoration: const InputDecoration(
|
||||
@@ -256,9 +252,11 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
await _status
|
||||
.client(left)!
|
||||
.rename(file.filename, textController.text);
|
||||
Navigator.of(context).pop();
|
||||
listDir(left);
|
||||
},
|
||||
child: const Text(
|
||||
'Create',
|
||||
'Rename',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)),
|
||||
]);
|
||||
@@ -278,17 +276,24 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
return '$finalValue ${suffix[squareTimes]}';
|
||||
}
|
||||
|
||||
Future<void> listDir(String path, bool left, {SSHClient? client}) async {
|
||||
Future<void> listDir(bool left, {String? path, SSHClient? client}) async {
|
||||
if (_status.isBusy(left)) {
|
||||
return;
|
||||
}
|
||||
_status.setBusy(left, true);
|
||||
if (client != null) {
|
||||
final sftpc = await client.sftp();
|
||||
_status.setClient(left, sftpc);
|
||||
}
|
||||
final fs = await _status.client(left)!.listdir(path);
|
||||
final fs = await _status
|
||||
.client(left)!
|
||||
.listdir(path ?? (_status.leftPath?.path ?? '/'));
|
||||
fs.sort((a, b) => a.filename.compareTo(b.filename));
|
||||
fs.removeAt(0);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_status.setFiles(left, fs);
|
||||
_status.setBusy(left, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -328,7 +333,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
|
||||
return Text(
|
||||
(exceeded ? '...' : '') + str!.substring(str.length - len),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
);
|
||||
@@ -348,11 +353,12 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
_status.setSpi(left, spi);
|
||||
_status.setSelect(left, true);
|
||||
_status.setPath(left, AbsolutePath('/'));
|
||||
listDir('/', left,
|
||||
listDir(left,
|
||||
client: locator<ServerProvider>()
|
||||
.servers
|
||||
.firstWhere((s) => s.info == spi)
|
||||
.client);
|
||||
.client,
|
||||
path: '/');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
3
lib/view/widget/center_loading.dart
Normal file
3
lib/view/widget/center_loading.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const centerLoading = Center(child: CircularProgressIndicator());
|
||||
@@ -362,7 +362,7 @@ packages:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
version: "2.0.9"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user