This commit is contained in:
lollipopkit
2024-03-18 18:34:25 +08:00
parent 8bfb0eb9e0
commit 26264ecdea
31 changed files with 1114 additions and 56 deletions

View File

@@ -20,6 +20,8 @@ enum ServerFuncBtn {
snippet,
@HiveField(6)
iperf,
@HiveField(7)
pve,
;
IconData get icon => switch (this) {
@@ -30,6 +32,7 @@ enum ServerFuncBtn {
process => Icons.list_alt_outlined,
terminal => Icons.terminal,
iperf => Icons.speed,
pve => Icons.computer,
};
String get toStr => switch (this) {
@@ -40,6 +43,7 @@ enum ServerFuncBtn {
process => l10n.process,
terminal => l10n.terminal,
iperf => 'iperf',
pve => 'PVE',
};
int toJson() => index;

View File

@@ -27,6 +27,8 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
return ServerFuncBtn.snippet;
case 6:
return ServerFuncBtn.iperf;
case 7:
return ServerFuncBtn.pve;
default:
return ServerFuncBtn.terminal;
}
@@ -56,6 +58,9 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
case ServerFuncBtn.iperf:
writer.writeByte(6);
break;
case ServerFuncBtn.pve:
writer.writeByte(7);
break;
}
}

View File

@@ -0,0 +1,36 @@
import 'package:hive_flutter/adapters.dart';
part 'custom.g.dart';
@HiveType(typeId: 7)
final class ServerCustom {
@HiveField(0)
final String? temperature;
@HiveField(1)
final String? pveAddr;
const ServerCustom({
this.temperature,
this.pveAddr,
});
static ServerCustom fromJson(Map<String, dynamic> json) {
final temperature = json["temperature"] as String?;
final pveAddr = json["pveAddr"] as String?;
return ServerCustom(
temperature: temperature,
pveAddr: pveAddr,
);
}
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (temperature != null) {
json["temperature"] = temperature;
}
if (pveAddr != null) {
json["pveAddr"] = pveAddr;
}
return json;
}
}

View File

@@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'custom.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ServerCustomAdapter extends TypeAdapter<ServerCustom> {
@override
final int typeId = 7;
@override
ServerCustom read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ServerCustom(
temperature: fields[0] as String?,
pveAddr: fields[1] as String?,
);
}
@override
void write(BinaryWriter writer, ServerCustom obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.temperature)
..writeByte(1)
..write(obj.pveAddr);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServerCustomAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,351 @@
import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/extension/duration.dart';
import 'package:toolbox/core/extension/numx.dart';
enum PveResType {
lxc,
qemu,
node,
storage,
sdn,
;
static PveResType fromString(String type) {
switch (type) {
case 'lxc':
return PveResType.lxc;
case 'qemu':
return PveResType.qemu;
case 'node':
return PveResType.node;
case 'storage':
return PveResType.storage;
case 'sdn':
return PveResType.sdn;
default:
throw Exception('Unknown PveResType: $type');
}
}
String get toStr => switch (this) {
PveResType.node => l10n.node,
PveResType.qemu => 'QEMU',
PveResType.lxc => 'LXC',
PveResType.storage => l10n.storage,
PveResType.sdn => 'SDN',
};
}
sealed class PveResIface {
String get id;
String get status;
PveResType get type;
static PveResIface fromJson(Map<String, dynamic> json) {
final type = PveResType.fromString(json['type']);
switch (type) {
case PveResType.lxc:
return PveLxc.fromJson(json);
case PveResType.qemu:
return PveQemu.fromJson(json);
case PveResType.node:
return PveNode.fromJson(json);
case PveResType.storage:
return PveStorage.fromJson(json);
case PveResType.sdn:
return PveSdn.fromJson(json);
}
}
}
final class PveLxc extends PveResIface {
@override
final String id;
@override
final PveResType type;
final int vmid;
final String node;
final String name;
@override
final String status;
final int uptime;
final int mem;
final int maxmem;
final double cpu;
final int maxcpu;
final int disk;
final int maxdisk;
final int diskread;
final int diskwrite;
final int netin;
final int netout;
PveLxc({
required this.id,
required this.type,
required this.vmid,
required this.node,
required this.name,
required this.status,
required this.uptime,
required this.mem,
required this.maxmem,
required this.cpu,
required this.maxcpu,
required this.disk,
required this.maxdisk,
required this.diskread,
required this.diskwrite,
required this.netin,
required this.netout,
});
static PveLxc fromJson(Map<String, dynamic> json) {
return PveLxc(
id: json['id'],
type: PveResType.lxc,
vmid: json['vmid'],
node: json['node'],
name: json['name'],
status: json['status'],
uptime: json['uptime'],
mem: json['mem'],
maxmem: json['maxmem'],
cpu: (json['cpu'] as num).toDouble(),
maxcpu: json['maxcpu'],
disk: json['disk'],
maxdisk: json['maxdisk'],
diskread: json['diskread'],
diskwrite: json['diskwrite'],
netin: json['netin'],
netout: json['netout'],
);
}
}
final class PveQemu extends PveResIface {
@override
final String id;
@override
final PveResType type;
final int vmid;
final String node;
final String name;
@override
final String status;
final int uptime;
final int mem;
final int maxmem;
final double cpu;
final int maxcpu;
final int disk;
final int maxdisk;
final int diskread;
final int diskwrite;
final int netin;
final int netout;
PveQemu({
required this.id,
required this.type,
required this.vmid,
required this.node,
required this.name,
required this.status,
required this.uptime,
required this.mem,
required this.maxmem,
required this.cpu,
required this.maxcpu,
required this.disk,
required this.maxdisk,
required this.diskread,
required this.diskwrite,
required this.netin,
required this.netout,
});
static PveQemu fromJson(Map<String, dynamic> json) {
return PveQemu(
id: json['id'],
type: PveResType.qemu,
vmid: json['vmid'],
node: json['node'],
name: json['name'],
status: json['status'],
uptime: json['uptime'],
mem: json['mem'],
maxmem: json['maxmem'],
cpu: (json['cpu'] as num).toDouble(),
maxcpu: json['maxcpu'],
disk: json['disk'],
maxdisk: json['maxdisk'],
diskread: json['diskread'],
diskwrite: json['diskwrite'],
netin: json['netin'],
netout: json['netout'],
);
}
bool get isRunning => status == 'running';
String get topRight {
if (!isRunning) {
return uptime.secondsToDuration().toStr;
}
return l10n.stopped;
}
}
final class PveNode extends PveResIface {
@override
final String id;
@override
final PveResType type;
final String node;
@override
final String status;
final int uptime;
final int mem;
final int maxmem;
final double cpu;
final int maxcpu;
PveNode({
required this.id,
required this.type,
required this.node,
required this.status,
required this.uptime,
required this.mem,
required this.maxmem,
required this.cpu,
required this.maxcpu,
});
static PveNode fromJson(Map<String, dynamic> json) {
return PveNode(
id: json['id'],
type: PveResType.node,
node: json['node'],
status: json['status'],
uptime: json['uptime'],
mem: json['mem'],
maxmem: json['maxmem'],
cpu: (json['cpu'] as num).toDouble(),
maxcpu: json['maxcpu'],
);
}
}
final class PveStorage extends PveResIface {
@override
final String id;
@override
final PveResType type;
final String storage;
final String node;
@override
final String status;
final String plugintype;
final String content;
final int shared;
final int disk;
final int maxdisk;
PveStorage({
required this.id,
required this.type,
required this.storage,
required this.node,
required this.status,
required this.plugintype,
required this.content,
required this.shared,
required this.disk,
required this.maxdisk,
});
static PveStorage fromJson(Map<String, dynamic> json) {
return PveStorage(
id: json['id'],
type: PveResType.storage,
storage: json['storage'],
node: json['node'],
status: json['status'],
plugintype: json['plugintype'],
content: json['content'],
shared: json['shared'],
disk: json['disk'],
maxdisk: json['maxdisk'],
);
}
}
final class PveSdn extends PveResIface {
@override
final String id;
@override
final PveResType type;
final String sdn;
final String node;
@override
final String status;
PveSdn({
required this.id,
required this.type,
required this.sdn,
required this.node,
required this.status,
});
static PveSdn fromJson(Map<String, dynamic> json) {
return PveSdn(
id: json['id'],
type: PveResType.sdn,
sdn: json['sdn'],
node: json['node'],
status: json['status'],
);
}
}
final class PveRes {
final List<PveNode> nodes;
final List<PveQemu> qemus;
final List<PveLxc> lxcs;
final List<PveStorage> storages;
final List<PveSdn> sdns;
const PveRes({
required this.nodes,
required this.qemus,
required this.lxcs,
required this.storages,
required this.sdns,
});
int get length =>
qemus.length + lxcs.length + nodes.length + storages.length + sdns.length;
PveResIface operator [](int index) {
if (index < nodes.length) {
return nodes[index];
}
index -= nodes.length;
if (index < qemus.length) {
return qemus[index];
}
index -= qemus.length;
if (index < lxcs.length) {
return lxcs[index];
}
index -= lxcs.length;
if (index < storages.length) {
return storages[index];
}
index -= storages.length;
return sdns[index];
}
}

View File

@@ -1,4 +1,5 @@
import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/model/server/custom.dart';
import 'package:toolbox/data/model/server/server.dart';
import 'package:toolbox/data/res/provider.dart';
@@ -6,6 +7,7 @@ import '../app/error.dart';
part 'server_private_info.g.dart';
/// In former version, it's called `ServerPrivateInfo`.
@HiveType(typeId: 3)
class ServerPrivateInfo {
@HiveField(0)
@@ -33,6 +35,9 @@ class ServerPrivateInfo {
@HiveField(9)
final String? jumpId;
@HiveField(10)
final ServerCustom? custom;
final String id;
const ServerPrivateInfo({
@@ -46,6 +51,7 @@ class ServerPrivateInfo {
this.alterUrl,
this.autoConnect,
this.jumpId,
this.custom,
}) : id = '$user@$ip:$port';
static ServerPrivateInfo fromJson(Map<String, dynamic> json) {
@@ -59,6 +65,9 @@ class ServerPrivateInfo {
final alterUrl = json["alterUrl"] as String?;
final autoConnect = json["autoConnect"] as bool?;
final jumpId = json["jumpId"] as String?;
final custom = json["customCmd"] == null
? null
: ServerCustom.fromJson(json["custom"].cast<String, dynamic>());
return ServerPrivateInfo(
name: name,
@@ -71,6 +80,7 @@ class ServerPrivateInfo {
alterUrl: alterUrl,
autoConnect: autoConnect,
jumpId: jumpId,
custom: custom,
);
}
@@ -80,12 +90,27 @@ class ServerPrivateInfo {
data["ip"] = ip;
data["port"] = port;
data["user"] = user;
data["authorization"] = pwd;
data["pubKeyId"] = keyId;
data["tags"] = tags;
data["alterUrl"] = alterUrl;
data["autoConnect"] = autoConnect;
data["jumpId"] = jumpId;
if (pwd != null) {
data["authorization"] = pwd;
}
if (keyId != null) {
data["pubKeyId"] = keyId;
}
if (tags != null) {
data["tags"] = tags;
}
if (alterUrl != null) {
data["alterUrl"] = alterUrl;
}
if (autoConnect != null) {
data["autoConnect"] = autoConnect;
}
if (jumpId != null) {
data["jumpId"] = jumpId;
}
if (custom != null) {
data["custom"] = custom?.toJson();
}
return data;
}

View File

@@ -27,13 +27,14 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
alterUrl: fields[7] as String?,
autoConnect: fields[8] as bool?,
jumpId: fields[9] as String?,
custom: fields[10] as ServerCustom?,
);
}
@override
void write(BinaryWriter writer, ServerPrivateInfo obj) {
writer
..writeByte(10)
..writeByte(11)
..writeByte(0)
..write(obj.name)
..writeByte(1)
@@ -53,7 +54,9 @@ class ServerPrivateInfoAdapter extends TypeAdapter<ServerPrivateInfo> {
..writeByte(8)
..write(obj.autoConnect)
..writeByte(9)
..write(obj.jumpId);
..write(obj.jumpId)
..writeByte(10)
..write(obj.custom);
}
@override

112
lib/data/provider/pve.dart Normal file
View File

@@ -0,0 +1,112 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:toolbox/data/model/server/pve.dart';
import 'package:toolbox/data/model/server/server_private_info.dart';
final class PveProvider extends ChangeNotifier {
final ServerPrivateInfo spi;
late final String addr;
//late final SSHClient _client;
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().then((_) => connected.complete());
}
final err = ValueNotifier<String?>(null);
final connected = Completer<void>();
final session = Dio();
// int _localPort = 0;
// String get addr => 'http://127.0.0.1:$_localPort';
Future<void> _init() async {
//await _forward();
await _login();
}
// Future<void> _forward() async {
// var retries = 0;
// while (retries < 3) {
// try {
// _localPort = Random().nextInt(1000) + 37000;
// print('Forwarding local port $_localPort');
// final serverSocket = await ServerSocket.bind('localhost', _localPort);
// final forward = await _client.forwardLocal('127.0.0.1', 8006);
// serverSocket.listen((socket) {
// forward.stream.cast<List<int>>().pipe(socket);
// socket.pipe(forward.sink);
// });
// return;
// } on SocketException {
// retries++;
// }
// }
// throw Exception('Failed to bind local port');
// }
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'
});
final ticket = resp.data['data']['ticket'];
session.options.headers['CSRFPreventionToken'] =
resp.data['data']['CSRFPreventionToken'];
session.options.headers['Cookie'] = 'PVEAuthCookie=$ticket';
}
Future<PveRes> list() async {
await connected.future;
final resp = await session.get('$addr/api2/json/cluster/resources');
final list = resp.data['data'] as List;
final items = list.map((e) => PveResIface.fromJson(e)).toList();
final qemus = <PveQemu>[];
final lxcs = <PveLxc>[];
final nodes = <PveNode>[];
final storages = <PveStorage>[];
final sdns = <PveSdn>[];
for (final item in items) {
switch (item.type) {
case PveResType.lxc:
lxcs.add(item as PveLxc);
break;
case PveResType.qemu:
qemus.add(item as PveQemu);
break;
case PveResType.node:
nodes.add(item as PveNode);
break;
case PveResType.storage:
storages.add(item as PveStorage);
break;
case PveResType.sdn:
sdns.add(item as PveSdn);
break;
}
}
return PveRes(
qemus: qemus,
lxcs: lxcs,
nodes: nodes,
storages: storages,
sdns: sdns,
);
}
}