mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
203 lines
5.9 KiB
Dart
203 lines
5.9 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:computer/computer.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:dio/io.dart';
|
|
import 'package:fl_lib/fl_lib.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:toolbox/core/extension/context/locale.dart';
|
|
import 'package:toolbox/data/model/app/error.dart';
|
|
import 'package:toolbox/data/model/server/pve.dart';
|
|
import 'package:toolbox/data/model/server/server_private_info.dart';
|
|
import 'package:dartssh2/dartssh2.dart';
|
|
|
|
typedef PveCtrlFunc = Future<bool> Function(String node, String id);
|
|
|
|
final class PveProvider extends ChangeNotifier {
|
|
final ServerPrivateInfo spi;
|
|
late String addr;
|
|
late final SSHClient _client;
|
|
late final ServerSocket _serverSocket;
|
|
final List<SSHForwardChannel> _forwards = [];
|
|
int _localPort = 0;
|
|
|
|
PveProvider({required this.spi}) {
|
|
final client = spi.server?.client;
|
|
if (client == null) {
|
|
throw Exception('Server client is null');
|
|
}
|
|
_client = client;
|
|
final addr = spi.custom?.pveAddr;
|
|
if (addr == null) {
|
|
err.value = 'PVE address is null';
|
|
return;
|
|
}
|
|
this.addr = addr;
|
|
_init();
|
|
}
|
|
|
|
final err = ValueNotifier<String?>(null);
|
|
final connected = Completer<void>();
|
|
|
|
late bool _ignoreCert = spi.custom?.pveIgnoreCert ?? false;
|
|
late final session = Dio()
|
|
..httpClientAdapter = IOHttpClientAdapter(
|
|
createHttpClient: () {
|
|
final client = HttpClient();
|
|
client.connectionFactory = cf;
|
|
if (_ignoreCert) {
|
|
client.badCertificateCallback = (_, __, ___) => true;
|
|
}
|
|
return client;
|
|
},
|
|
validateCertificate: _ignoreCert ? (_, __, ___) => true : null,
|
|
);
|
|
|
|
final data = ValueNotifier<PveRes?>(null);
|
|
|
|
bool get onlyOneNode => data.value?.nodes.length == 1;
|
|
String? release;
|
|
bool isBusy = false;
|
|
|
|
Future<void> _init() async {
|
|
try {
|
|
await _forward();
|
|
await _login();
|
|
await _release;
|
|
} on PveErr {
|
|
err.value = l10n.pveLoginFailed;
|
|
} catch (e) {
|
|
Loggers.app.warning('PVE init failed', e);
|
|
err.value = e.toString();
|
|
} finally {
|
|
connected.complete();
|
|
}
|
|
}
|
|
|
|
Future<void> _forward() async {
|
|
final url = Uri.parse(addr);
|
|
if (_localPort == 0) {
|
|
_serverSocket = await ServerSocket.bind('localhost', 0);
|
|
_localPort = _serverSocket.port;
|
|
_serverSocket.listen((socket) async {
|
|
final forward = await _client.forwardLocal(url.host, url.port);
|
|
_forwards.add(forward);
|
|
forward.stream.cast<List<int>>().pipe(socket);
|
|
socket.cast<List<int>>().pipe(forward.sink);
|
|
});
|
|
final newUrl = Uri.parse(addr)
|
|
.replace(host: 'localhost', port: _localPort)
|
|
.toString();
|
|
print('Forwarding $newUrl to $addr');
|
|
}
|
|
}
|
|
|
|
Future<ConnectionTask<Socket>> cf(
|
|
Uri url, String? proxyHost, int? proxyPort) async {
|
|
/* final serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, 0);
|
|
final _localPort = serverSocket.port;
|
|
serverSocket.listen((socket) async {
|
|
final forward = await _client.forwardLocal(url.host, url.port);
|
|
forwards.add(forward);
|
|
forward.stream.cast<List<int>>().pipe(socket);
|
|
socket.cast<List<int>>().pipe(forward.sink);
|
|
});*/
|
|
|
|
if (url.isScheme("https")) {
|
|
return SecureSocket.startConnect('localhost', _localPort,
|
|
onBadCertificate: (_) => true);
|
|
} else {
|
|
return Socket.startConnect('localhost', _localPort);
|
|
}
|
|
}
|
|
|
|
Future<void> _login() async {
|
|
final resp = await session.post('$addr/api2/extjs/access/ticket',
|
|
data: {
|
|
'username': spi.user,
|
|
'password': spi.pwd,
|
|
'realm': 'pam',
|
|
'new-format': '1'
|
|
},
|
|
options: Options(
|
|
headers: {HttpHeaders.contentTypeHeader: Headers.jsonContentType}));
|
|
try {
|
|
final ticket = resp.data['data']['ticket'];
|
|
session.options.headers['CSRFPreventionToken'] =
|
|
resp.data['data']['CSRFPreventionToken'];
|
|
session.options.headers['Cookie'] = 'PVEAuthCookie=$ticket';
|
|
} catch (e) {
|
|
throw PveErr(type: PveErrType.loginFailed, message: e.toString());
|
|
}
|
|
}
|
|
|
|
/// Returns true if the PVE version is 8.0 or later
|
|
Future<void> get _release async {
|
|
final resp = await session.get('$addr/api2/extjs/version');
|
|
final version = resp.data['data']['release'] as String?;
|
|
if (version != null) {
|
|
release = version;
|
|
}
|
|
}
|
|
|
|
Future<void> list() async {
|
|
await connected.future;
|
|
if (isBusy) return;
|
|
isBusy = true;
|
|
try {
|
|
final resp = await session.get('$addr/api2/json/cluster/resources');
|
|
final res = resp.data['data'] as List;
|
|
final result =
|
|
await Computer.shared.start(PveRes.parse, (res, data.value));
|
|
data.value = result;
|
|
} catch (e) {
|
|
Loggers.app.warning('PVE list failed', e);
|
|
err.value = e.toString();
|
|
} finally {
|
|
isBusy = false;
|
|
}
|
|
}
|
|
|
|
Future<bool> reboot(String node, String id) async {
|
|
await connected.future;
|
|
final resp =
|
|
await session.post('$addr/api2/json/nodes/$node/$id/status/reboot');
|
|
return _isCtrlSuc(resp);
|
|
}
|
|
|
|
Future<bool> start(String node, String id) async {
|
|
await connected.future;
|
|
final resp =
|
|
await session.post('$addr/api2/json/nodes/$node/$id/status/start');
|
|
return _isCtrlSuc(resp);
|
|
}
|
|
|
|
Future<bool> stop(String node, String id) async {
|
|
await connected.future;
|
|
final resp =
|
|
await session.post('$addr/api2/json/nodes/$node/$id/status/stop');
|
|
return _isCtrlSuc(resp);
|
|
}
|
|
|
|
Future<bool> shutdown(String node, String id) async {
|
|
await connected.future;
|
|
final resp =
|
|
await session.post('$addr/api2/json/nodes/$node/$id/status/shutdown');
|
|
return _isCtrlSuc(resp);
|
|
}
|
|
|
|
bool _isCtrlSuc(Response resp) {
|
|
return resp.statusCode == 200;
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
super.dispose();
|
|
await _serverSocket.close();
|
|
for (final forward in _forwards) {
|
|
forward.close();
|
|
}
|
|
}
|
|
}
|