Skip to content

Commit bfbef19

Browse files
committed
feat(PVE): Added support for PVE passwords during key-based authentication
- Added the `pvePwd` field to the `ServerCustom` model - Added a PVE password input field to the edit page (displayed only during key-based authentication) - Updated multilingual files to support PVE-related loading states and password prompts - Optimized PVE connection logic to support password verification during key-based authentication
1 parent 7d7156a commit bfbef19

28 files changed

+435
-60
lines changed

lib/data/model/server/custom.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ final class ServerCustom {
1111

1212
final bool pveIgnoreCert;
1313

14+
final String? pvePwd;
15+
1416
/// {"title": "cmd"}
1517
final Map<String, String>? cmds;
1618

@@ -28,6 +30,7 @@ final class ServerCustom {
2830
//this.temperature,
2931
this.pveAddr,
3032
this.pveIgnoreCert = false,
33+
this.pvePwd,
3134
this.cmds,
3235
this.preferTempDev,
3336
this.logoUrl,
@@ -45,6 +48,7 @@ final class ServerCustom {
4548
//other.temperature == temperature &&
4649
other.pveAddr == pveAddr &&
4750
other.pveIgnoreCert == pveIgnoreCert &&
51+
other.pvePwd == pvePwd &&
4852
other.cmds == cmds &&
4953
other.preferTempDev == preferTempDev &&
5054
other.logoUrl == logoUrl &&
@@ -57,6 +61,7 @@ final class ServerCustom {
5761
//temperature.hashCode ^
5862
pveAddr.hashCode ^
5963
pveIgnoreCert.hashCode ^
64+
pvePwd.hashCode ^
6065
cmds.hashCode ^
6166
preferTempDev.hashCode ^
6267
logoUrl.hashCode ^

lib/data/model/server/custom.g.dart

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/data/provider/pve.dart

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'package:dio/io.dart';
88
import 'package:fl_lib/fl_lib.dart';
99
import 'package:freezed_annotation/freezed_annotation.dart';
1010
import 'package:riverpod_annotation/riverpod_annotation.dart';
11-
import 'package:server_box/core/extension/context/locale.dart';
1211
import 'package:server_box/data/model/app/error.dart';
1312
import 'package:server_box/data/model/server/pve.dart';
1413
import 'package:server_box/data/model/server/server_private_info.dart';
@@ -40,12 +39,15 @@ abstract class PveState with _$PveState {
4039

4140
@riverpod
4241
class PveNotifier extends _$PveNotifier {
43-
late String addr;
44-
late final ServerSocket _serverSocket;
42+
String? addr;
43+
ServerSocket? _serverSocket;
4544
final List<SSHForwardChannel> _forwards = [];
4645
int _localPort = 0;
47-
late final Dio session;
48-
late final bool _ignoreCert;
46+
Dio? _session;
47+
bool _ignoreCert = false;
48+
49+
Dio get session => _session!;
50+
String get addrValue => addr!;
4951

5052
SSHClient get _client {
5153
final serverState = ref.read(serverProvider(spiParam.id));
@@ -59,23 +61,23 @@ class PveNotifier extends _$PveNotifier {
5961
@override
6062
PveState build(Spi spiParam) {
6163
ref.onDispose(() => dispose());
62-
final serverState = ref.read(serverProvider(spiParam.id));
64+
final serverState = ref.watch(serverProvider(spiParam.id));
6365
if (serverState.client == null) {
64-
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));
66+
return const PveState(loadingStep: PveLoadingStep.forwarding);
6567
}
66-
final addr = spiParam.custom?.pveAddr;
67-
if (addr == null) {
68+
final pveAddr = spiParam.custom?.pveAddr;
69+
if (pveAddr == null) {
6870
return PveState(error: PveErr(type: PveErrType.net, message: 'PVE address is null'));
6971
}
70-
this.addr = addr;
72+
addr = pveAddr;
7173
_ignoreCert = spiParam.custom?.pveIgnoreCert ?? false;
7274
_initSession();
7375
Future.microtask(() => _init());
7476
return const PveState(loadingStep: PveLoadingStep.forwarding);
7577
}
7678

7779
void _initSession() {
78-
session = Dio()
80+
_session = Dio()
7981
..httpClientAdapter = IOHttpClientAdapter(
8082
createHttpClient: () {
8183
final client = HttpClient();
@@ -91,6 +93,11 @@ class PveNotifier extends _$PveNotifier {
9193

9294
bool get onlyOneNode => state.data?.nodes.length == 1;
9395

96+
Future<void> reconnect() async {
97+
state = state.copyWith(error: null, isConnected: false, loadingStep: PveLoadingStep.forwarding);
98+
await _init();
99+
}
100+
94101
Future<void> _init() async {
95102
try {
96103
if (!ref.mounted) return;
@@ -118,11 +125,11 @@ class PveNotifier extends _$PveNotifier {
118125
}
119126

120127
Future<void> _forward() async {
121-
final url = Uri.parse(addr);
128+
final url = Uri.parse(addrValue);
122129
if (_localPort == 0) {
123130
_serverSocket = await ServerSocket.bind('localhost', 0);
124-
_localPort = _serverSocket.port;
125-
_serverSocket.listen((socket) async {
131+
_localPort = _serverSocket!.port;
132+
_serverSocket!.listen((socket) async {
126133
try {
127134
final forward = await _client.forwardLocal(url.host, url.port);
128135
_forwards.add(forward);
@@ -133,9 +140,6 @@ class PveNotifier extends _$PveNotifier {
133140
socket.destroy();
134141
}
135142
});
136-
final newUrl = Uri.parse(
137-
addr,
138-
).replace(host: 'localhost', port: _localPort).toString();
139143
}
140144
}
141145

@@ -156,11 +160,16 @@ class PveNotifier extends _$PveNotifier {
156160
}
157161

158162
Future<void> _login() async {
163+
final useKeyAuth = spiParam.keyId != null;
164+
final password = useKeyAuth ? spiParam.custom?.pvePwd : spiParam.pwd;
165+
if (password == null) {
166+
throw PveErr(type: PveErrType.loginFailed, message: 'PVE password is required. Please set it in server settings.');
167+
}
159168
final resp = await session.post(
160-
'$addr/api2/extjs/access/ticket',
169+
'$addrValue/api2/extjs/access/ticket',
161170
data: {
162171
'username': spiParam.user,
163-
'password': spiParam.pwd,
172+
'password': password,
164173
'realm': 'pam',
165174
'new-format': '1',
166175
},
@@ -185,7 +194,7 @@ class PveNotifier extends _$PveNotifier {
185194

186195
/// Returns true if the PVE version is 8.0 or later
187196
Future<void> _getRelease() async {
188-
final resp = await session.get('$addr/api2/extjs/version');
197+
final resp = await session.get('$addrValue/api2/extjs/version');
189198
final version = resp.data['data']['release'] as String?;
190199
if (version != null && ref.mounted) {
191200
state = state.copyWith(release: version);
@@ -196,7 +205,7 @@ class PveNotifier extends _$PveNotifier {
196205
if (!state.isConnected || state.isBusy) return;
197206
state = state.copyWith(isBusy: true);
198207
try {
199-
final resp = await session.get('$addr/api2/json/cluster/resources');
208+
final resp = await session.get('$addrValue/api2/json/cluster/resources');
200209
final res = resp.data['data'] as List;
201210
final result = await Computer.shared.start(PveRes.parse, (
202211
res,
@@ -218,7 +227,7 @@ class PveNotifier extends _$PveNotifier {
218227
Future<bool> reboot(String node, String id) async {
219228
if (!state.isConnected) return false;
220229
final resp = await session.post(
221-
'$addr/api2/json/nodes/$node/$id/status/reboot',
230+
'$addrValue/api2/json/nodes/$node/$id/status/reboot',
222231
);
223232
final success = _isCtrlSuc(resp);
224233
if (success) await list(); // Refresh data
@@ -228,7 +237,7 @@ class PveNotifier extends _$PveNotifier {
228237
Future<bool> start(String node, String id) async {
229238
if (!state.isConnected) return false;
230239
final resp = await session.post(
231-
'$addr/api2/json/nodes/$node/$id/status/start',
240+
'$addrValue/api2/json/nodes/$node/$id/status/start',
232241
);
233242
final success = _isCtrlSuc(resp);
234243
if (success) await list(); // Refresh data
@@ -238,7 +247,7 @@ class PveNotifier extends _$PveNotifier {
238247
Future<bool> stop(String node, String id) async {
239248
if (!state.isConnected) return false;
240249
final resp = await session.post(
241-
'$addr/api2/json/nodes/$node/$id/status/stop',
250+
'$addrValue/api2/json/nodes/$node/$id/status/stop',
242251
);
243252
final success = _isCtrlSuc(resp);
244253
if (success) await list(); // Refresh data
@@ -248,7 +257,7 @@ class PveNotifier extends _$PveNotifier {
248257
Future<bool> shutdown(String node, String id) async {
249258
if (!state.isConnected) return false;
250259
final resp = await session.post(
251-
'$addr/api2/json/nodes/$node/$id/status/shutdown',
260+
'$addrValue/api2/json/nodes/$node/$id/status/shutdown',
252261
);
253262
final success = _isCtrlSuc(resp);
254263
if (success) await list(); // Refresh data
@@ -261,7 +270,7 @@ class PveNotifier extends _$PveNotifier {
261270

262271
Future<void> dispose() async {
263272
try {
264-
await _serverSocket.close();
273+
await _serverSocket?.close();
265274
} catch (e, s) {
266275
Loggers.app.warning('Failed to close server socket', e, s);
267276
}

lib/data/provider/pve.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/generated/l10n/l10n.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,42 @@ abstract class AppLocalizations {
10621062
/// **'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.'**
10631063
String get pveVersionLow;
10641064

1065+
/// No description provided for @pveLoadingForwarding.
1066+
///
1067+
/// In en, this message translates to:
1068+
/// **'Establishing SSH tunnel...'**
1069+
String get pveLoadingForwarding;
1070+
1071+
/// No description provided for @pveLoadingLogin.
1072+
///
1073+
/// In en, this message translates to:
1074+
/// **'Authenticating with PVE...'**
1075+
String get pveLoadingLogin;
1076+
1077+
/// No description provided for @pveLoadingData.
1078+
///
1079+
/// In en, this message translates to:
1080+
/// **'Fetching cluster data...'**
1081+
String get pveLoadingData;
1082+
1083+
/// No description provided for @pveLoadingConnect.
1084+
///
1085+
/// In en, this message translates to:
1086+
/// **'Connecting...'**
1087+
String get pveLoadingConnect;
1088+
1089+
/// No description provided for @pvePassword.
1090+
///
1091+
/// In en, this message translates to:
1092+
/// **'PVE Password'**
1093+
String get pvePassword;
1094+
1095+
/// No description provided for @pvePasswordHint.
1096+
///
1097+
/// In en, this message translates to:
1098+
/// **'Required when using key-based SSH authentication'**
1099+
String get pvePasswordHint;
1100+
10651101
/// No description provided for @read.
10661102
///
10671103
/// In en, this message translates to:

lib/generated/l10n/l10n_de.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,25 @@ class AppLocalizationsDe extends AppLocalizations {
546546
String get pveVersionLow =>
547547
'Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.';
548548

549+
@override
550+
String get pveLoadingForwarding => 'Establishing SSH tunnel...';
551+
552+
@override
553+
String get pveLoadingLogin => 'Authenticating with PVE...';
554+
555+
@override
556+
String get pveLoadingData => 'Fetching cluster data...';
557+
558+
@override
559+
String get pveLoadingConnect => 'Connecting...';
560+
561+
@override
562+
String get pvePassword => 'PVE Password';
563+
564+
@override
565+
String get pvePasswordHint =>
566+
'Required when using key-based SSH authentication';
567+
549568
@override
550569
String get read => 'Lesen';
551570

lib/generated/l10n/l10n_en.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,25 @@ class AppLocalizationsEn extends AppLocalizations {
543543
String get pveVersionLow =>
544544
'This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.';
545545

546+
@override
547+
String get pveLoadingForwarding => 'Establishing SSH tunnel...';
548+
549+
@override
550+
String get pveLoadingLogin => 'Authenticating with PVE...';
551+
552+
@override
553+
String get pveLoadingData => 'Fetching cluster data...';
554+
555+
@override
556+
String get pveLoadingConnect => 'Connecting...';
557+
558+
@override
559+
String get pvePassword => 'PVE Password';
560+
561+
@override
562+
String get pvePasswordHint =>
563+
'Required when using key-based SSH authentication';
564+
546565
@override
547566
String get read => 'Read';
548567

lib/generated/l10n/l10n_es.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,25 @@ class AppLocalizationsEs extends AppLocalizations {
548548
String get pveVersionLow =>
549549
'Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.';
550550

551+
@override
552+
String get pveLoadingForwarding => 'Establishing SSH tunnel...';
553+
554+
@override
555+
String get pveLoadingLogin => 'Authenticating with PVE...';
556+
557+
@override
558+
String get pveLoadingData => 'Fetching cluster data...';
559+
560+
@override
561+
String get pveLoadingConnect => 'Connecting...';
562+
563+
@override
564+
String get pvePassword => 'PVE Password';
565+
566+
@override
567+
String get pvePasswordHint =>
568+
'Required when using key-based SSH authentication';
569+
551570
@override
552571
String get read => 'Leer';
553572

lib/generated/l10n/l10n_fr.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,25 @@ class AppLocalizationsFr extends AppLocalizations {
550550
String get pveVersionLow =>
551551
'Cette fonctionnalité est actuellement en phase de test et n\'a été testée que sur PVE 8+. Veuillez l\'utiliser avec prudence.';
552552

553+
@override
554+
String get pveLoadingForwarding => 'Establishing SSH tunnel...';
555+
556+
@override
557+
String get pveLoadingLogin => 'Authenticating with PVE...';
558+
559+
@override
560+
String get pveLoadingData => 'Fetching cluster data...';
561+
562+
@override
563+
String get pveLoadingConnect => 'Connecting...';
564+
565+
@override
566+
String get pvePassword => 'PVE Password';
567+
568+
@override
569+
String get pvePasswordHint =>
570+
'Required when using key-based SSH authentication';
571+
553572
@override
554573
String get read => 'Lire';
555574

0 commit comments

Comments
 (0)