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/stringx.dart'; import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/docker/image.dart'; import 'package:toolbox/data/model/docker/ps.dart'; import 'package:toolbox/data/model/app/error.dart'; import 'package:toolbox/data/store/docker.dart'; import 'package:toolbox/locator.dart'; import '../res/server_cmd.dart'; final _dockerNotFound = RegExp(r'command not found|Unknown command'); final _versionReg = RegExp(r'(Version:)\s+([0-9]+\.[0-9]+\.[0-9]+)'); // eg: `Docker Engine - Community` final _editionReg = RegExp(r'Docker Engine - [a-zA-Z]+'); final _dockerPrefixReg = RegExp(r'(sudo )?docker '); final _logger = Logger('DOCKER'); class DockerProvider extends ChangeNotifier { final _dockerStore = locator(); SSHClient? client; String? userName; List? items; List? images; String? version; String? edition; DockerErr? error; String? hostId; String? runLog; BuildContext? context; void init( SSHClient client, String userName, PwdRequestFunc onPwdReq, String hostId, BuildContext context, ) { this.client = client; this.userName = userName; this.context = context; this.hostId = hostId; } void clear() { client = userName = error = items = version = edition = context = null; hostId = runLog = images = null; } Future refresh() async { var raw = ''; await client!.execWithPwd( AppShellFuncType.docker.exec, context: context, onStdout: (data, _) => raw = '$raw$data', ); if (raw.contains(_dockerNotFound)) { error = DockerErr(type: DockerErrType.notInstalled); _logger.warning('Docker not installed: $raw'); notifyListeners(); return; } // Check result segments count final segments = raw.split(seperator); if (segments.length != DockerCmdType.values.length) { error = DockerErr(type: DockerErrType.segmentsNotMatch); _logger.warning('Docker segments not match: ${segments.length}'); notifyListeners(); return; } // Parse docker version final verRaw = DockerCmdType.version.find(segments); version = _versionReg.firstMatch(verRaw)?.group(2); edition = _editionReg.firstMatch(verRaw)?.group(0); // Parse docker ps final psRaw = DockerCmdType.ps.find(segments); try { final lines = psRaw.split('\n'); lines.removeWhere((element) => element.isEmpty); if (lines.isNotEmpty) lines.removeAt(0); items = lines.map((e) => DockerPsItem.fromRawString(e)).toList(); } catch (e) { error = DockerErr( type: DockerErrType.parsePsItem, message: '$psRaw\n-\n$e', ); _logger.warning('Parse docker ps: $psRaw', e); } finally { notifyListeners(); } // Parse docker images final imageRaw = DockerCmdType.images.find(segments); try { final imageLines = imageRaw.split('\n'); imageLines.removeWhere((element) => element.isEmpty); if (imageLines.isNotEmpty) imageLines.removeAt(0); images = imageLines.map((e) => DockerImage.fromRawStr(e)).toList(); } catch (e, trace) { error = DockerErr( type: DockerErrType.parseImages, message: '$imageRaw\n-\n$e', ); _logger.warning('Parse docker images: $imageRaw', e, trace); } finally { notifyListeners(); } // Parse docker stats // final statsRaw = DockerCmdType.stats.find(segments); // try { // final statsLines = statsRaw.split('\n'); // statsLines.removeWhere((element) => element.isEmpty); // if (statsLines.isNotEmpty) statsLines.removeAt(0); // for (var item in items!) { // final statsLine = statsLines.firstWhere( // (element) => element.contains(item.containerId), // orElse: () => '', // ); // if (statsLine.isEmpty) continue; // item.parseStats(statsLine); // } // } catch (e, trace) { // error = DockerErr( // type: DockerErrType.parseStats, // message: '$statsRaw\n-\n$e', // ); // _logger.warning('Parse docker stats: $statsRaw', e, trace); // } finally { // notifyListeners(); // } } Future stop(String id) async => await run('docker stop $id'); Future start(String id) async => await run('docker start $id'); Future delete(String id) async => await run('docker rm $id'); Future restart(String id) async => await run('docker restart $id'); Future run(String cmd) async { if (!cmd.startsWith(_dockerPrefixReg)) { return DockerErr(type: DockerErrType.cmdNoPrefix); } runLog = ''; final errs = []; final code = await client!.execWithPwd( _wrap(cmd), context: context, onStdout: (data, _) { runLog = '$runLog$data'; notifyListeners(); }, onStderr: (data, _) => errs.add(data), ); runLog = null; notifyListeners(); if (code != 0) { return DockerErr( type: DockerErrType.unknown, message: errs.join('\n').trim(), ); } await refresh(); return null; } // judge whether to use DOCKER_HOST String _wrap(String cmd) { final dockerHost = _dockerStore.fetch(hostId!); if (dockerHost == null || dockerHost.isEmpty) { return cmd.withLangExport; } return 'export DOCKER_HOST=$dockerHost && $cmd'.withLangExport; } }