B extends Builder<V, B>> on ListBuilder<Built<V, B>> {
void rebuildWhere(bool Function(V) test, void Function(B) updates) {
for (var i = 0; i != this.length; ++i) {
- if (test(this[i])) this[i] = this[i].rebuild(updates);
+ if (test(this[i] as V)) this[i] = this[i].rebuild(updates);
}
}
}
class _ProxmoxGeneralSettingsFormState
extends State<ProxmoxGeneralSettingsForm> {
- Future<ProxmoxGeneralSettingsModel> _settings;
+ Future<ProxmoxGeneralSettingsModel>? _settings;
@override
void initState() {
super.initState();
future: _settings,
builder: (context, snaptshot) {
if (snaptshot.hasData) {
- final settings = snaptshot.data;
+ final settings = snaptshot.data!;
return SingleChildScrollView(
child: Column(
children: [
SwitchListTile(
title: Text('Validate SSL connections'),
subtitle: Text('e.g. validates certificates'),
- value: settings.sslValidation,
+ value: settings.sslValidation!,
onChanged: (value) async {
await settings
.rebuild((b) => b.sslValidation = value)
abstract class ProxmoxGeneralSettingsModel
implements
Built<ProxmoxGeneralSettingsModel, ProxmoxGeneralSettingsModelBuilder> {
- bool get sslValidation;
+ bool? get sslValidation;
ProxmoxGeneralSettingsModel._();
factory ProxmoxGeneralSettingsModel(
- [void Function(ProxmoxGeneralSettingsModelBuilder) updates]) =
+ [void Function(ProxmoxGeneralSettingsModelBuilder)? updates]) =
_$ProxmoxGeneralSettingsModel;
factory ProxmoxGeneralSettingsModel.defaultValues() =>
Object toJson() {
return serializers.serializeWith(
- ProxmoxGeneralSettingsModel.serializer, this);
+ ProxmoxGeneralSettingsModel.serializer, this) ??
+ {};
}
- static ProxmoxGeneralSettingsModel fromJson(Object json) {
+ static ProxmoxGeneralSettingsModel fromJson(Object? json) {
return serializers.deserializeWith(
- ProxmoxGeneralSettingsModel.serializer, json);
+ ProxmoxGeneralSettingsModel.serializer, json) ??
+ ProxmoxGeneralSettingsModel();
}
static Serializer<ProxmoxGeneralSettingsModel> get serializer =>
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('ProxmoxGeneralSettings')) {
final decodedJson =
- json.decode(prefs.getString('ProxmoxGeneralSettings'));
+ json.decode(prefs.getString('ProxmoxGeneralSettings')!);
return fromJson(decodedJson);
}
return ProxmoxGeneralSettingsModel.defaultValues();
import 'dart:io';
+import 'dart:async';
import 'package:flutter/material.dart';
import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'
import 'package:proxmox_login_manager/extension.dart';
class ProxmoxProgressModel {
- bool inProgress;
- String message;
+ bool inProgress = false;
+ String message = 'Loading...';
ProxmoxProgressModel({
this.inProgress = false,
this.message = 'Loading...',
final FormFieldValidator<String> originValidator;
final TextEditingController usernameController;
final TextEditingController passwordController;
- final List<PveAccessDomainModel> accessDomains;
- final PveAccessDomainModel selectedDomain;
- final ValueChanged<PveAccessDomainModel> onDomainChanged;
- final Function onPasswordSubmitted;
- final Function onOriginSubmitted;
+ final List<PveAccessDomainModel?>? accessDomains;
+ final PveAccessDomainModel? selectedDomain;
+ final ValueChanged<PveAccessDomainModel?> onDomainChanged;
+ final Function? onPasswordSubmitted;
+ final Function? onOriginSubmitted;
const ProxmoxLoginForm({
- Key key,
- @required this.originController,
- @required this.usernameController,
- @required this.passwordController,
- @required this.accessDomains,
- @required this.originValidator,
+ Key? key,
+ /*required*/ required this.originController,
+ /*required*/ /*required*/ required this.usernameController,
+ /*required*/ required this.passwordController,
+ /*required*/ required this.accessDomains,
+ /*required*/ required this.originValidator,
this.selectedDomain,
- @required this.onDomainChanged,
+ /*required*/ required this.onDomainChanged,
this.onPasswordSubmitted,
this.onOriginSubmitted,
}) : super(key: key);
class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
bool _obscure = true;
- FocusNode passwordFocusNode;
+ FocusNode? passwordFocusNode;
@override
Widget build(BuildContext context) {
helperText: 'Protocol (https) and default port (8006) implied'),
controller: widget.originController,
validator: widget.originValidator,
- onFieldSubmitted: (value) => widget.onOriginSubmitted(),
+ onFieldSubmitted: (value) => widget.onOriginSubmitted!(),
);
}
),
controller: widget.usernameController,
validator: (value) {
- if (value.isEmpty) {
+ if (value!.isEmpty) {
return 'Please enter username';
}
return null;
),
DropdownButtonFormField(
decoration: InputDecoration(icon: Icon(Icons.domain)),
- items: widget.accessDomains
+ items: widget.accessDomains!
.map((e) => DropdownMenuItem(
child: ListTile(
- title: Text(e.realm),
+ title: Text(e!.realm!),
subtitle: Text(e.comment ?? ''),
),
value: e,
.toList(),
onChanged: widget.onDomainChanged,
selectedItemBuilder: (context) =>
- widget.accessDomains.map((e) => Text(e.realm)).toList(),
+ widget.accessDomains!.map((e) => Text(e!.realm!)).toList(),
value: widget.selectedDomain,
),
Stack(
autocorrect: false,
focusNode: passwordFocusNode,
validator: (value) {
- if (value.isEmpty) {
+ if (value!.isEmpty) {
return 'Please enter password';
}
return null;
},
- onFieldSubmitted: (value) => widget.onPasswordSubmitted(),
+ onFieldSubmitted: (value) => widget.onPasswordSubmitted!(),
autofillHints: [AutofillHints.password],
),
Align(
}
class ProxmoxLoginPage extends StatefulWidget {
- final ProxmoxLoginModel userModel;
- final bool isCreate;
- final String ticket;
+ final ProxmoxLoginModel? userModel;
+ final bool? isCreate;
+ final String? ticket;
const ProxmoxLoginPage({
- Key key,
+ Key? key,
this.userModel,
this.isCreate,
this.ticket = '',
final _originController = TextEditingController();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
- Future<List<PveAccessDomainModel>> _accessDomains;
- PveAccessDomainModel _selectedDomain;
+ Future<List<PveAccessDomainModel?>?>? _accessDomains;
+ PveAccessDomainModel? _selectedDomain;
final _formKey = GlobalKey<FormState>();
- ProxmoxProgressModel _progressModel;
+ ProxmoxProgressModel _progressModel = ProxmoxProgressModel();
bool _submittButtonEnabled = true;
@override
super.initState();
final userModel = widget.userModel;
_progressModel = ProxmoxProgressModel();
- if (!widget.isCreate && userModel != null) {
+ if (!widget.isCreate! && userModel != null) {
_progressModel
..inProgress = true
..message = 'Connection test...';
_originController.text =
'${userModel.origin?.host}:${userModel.origin?.port}';
_accessDomains = _getAccessDomains();
- _usernameController.text = userModel.username;
- if (widget.ticket.isNotEmpty && userModel.activeSession) {
- _onLoginButtonPressed(ticket: widget.ticket, mRealm: userModel.realm);
+ _usernameController.text = userModel.username!;
+ if (widget.ticket!.isNotEmpty && userModel.activeSession) {
+ _onLoginButtonPressed(ticket: widget.ticket!, mRealm: userModel.realm);
}
}
}
height: MediaQuery.of(context).size.height),
child: Padding(
padding: const EdgeInsets.all(8.0),
- child: FutureBuilder<List<PveAccessDomainModel>>(
+ child: FutureBuilder<List<PveAccessDomainModel?>?>(
future: _accessDomains,
builder: (context, snapshot) {
return Form(
onChanged: () {
setState(() {
_submittButtonEnabled =
- _formKey.currentState.validate();
+ _formKey.currentState!.validate();
});
},
child: Column(
ProxmoxLoginForm(
originController: _originController,
originValidator: (value) {
- if (value.isEmpty) {
+ if (value == null || value.isEmpty) {
return 'Please enter origin';
}
if (value.startsWith('https://') ||
onOriginSubmitted: _submittButtonEnabled
? () {
final isValid =
- _formKey.currentState.validate();
+ _formKey.currentState!.validate();
setState(() {
_submittButtonEnabled = isValid;
});
onPasswordSubmitted: _submittButtonEnabled
? () {
final isValid =
- _formKey.currentState.validate();
+ _formKey.currentState!.validate();
setState(() {
_submittButtonEnabled = isValid;
});
onPressed: _submittButtonEnabled
? () {
final isValid = _formKey
- .currentState
+ .currentState!
.validate();
setState(() {
_submittButtonEnabled =
onPressed: _submittButtonEnabled
? () {
final isValid = _formKey
- .currentState
+ .currentState!
.validate();
setState(() {
_submittButtonEnabled =
}
Future<void> _onLoginButtonPressed(
- {String ticket = '', String mRealm}) async {
+ {String ticket = '', String? mRealm}) async {
setState(() {
_progressModel
..inProgress = true
final realm = _selectedDomain?.realm ?? mRealm;
var client = await proxclient.authenticate(
- '$username@$realm', password, origin, settings.sslValidation);
+ '$username@$realm', password, origin, settings.sslValidation!);
if (client.credentials.tfa) {
- client = await Navigator.of(context).push(MaterialPageRoute(
+ client = await (Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProxmoxTfaForm(
apiClient: client,
),
- ));
-
- if (client == null) {
- setState(() {
- _progressModel.inProgress = false;
- });
- return;
- }
+ )) as FutureOr<ProxmoxApiClient>);
}
final status = await client.getClusterStatus();
final hostname =
- status.singleWhere((element) => element.local ?? false).name;
+ status.singleWhere((element) => element!.local ?? false)!.name;
var loginStorage = await ProxmoxLoginStorage.fromLocalStorage();
- if (widget.isCreate) {
+ if (widget.isCreate!) {
final newLogin = ProxmoxLoginModel((b) => b
..origin = origin
..username = username
..ticket = client.credentials.ticket
..hostname = hostname);
- loginStorage = loginStorage.rebuild((b) => b..logins.add(newLogin));
+ loginStorage = loginStorage!.rebuild((b) => b..logins.add(newLogin));
} else {
- loginStorage = loginStorage.rebuild((b) => b
+ loginStorage = loginStorage!.rebuild((b) => b
..logins.rebuildWhere(
(m) => m == widget.userModel,
(b) => b
});
}
- Future<List<PveAccessDomainModel>> _getAccessDomains() async {
+ Future<List<PveAccessDomainModel?>?> _getAccessDomains() async {
setState(() {
_progressModel
..inProgress = true
}
final settings = await ProxmoxGeneralSettingsModel.fromLocalStorage();
- List<PveAccessDomainModel> response;
+ List<PveAccessDomainModel?>? response;
try {
response =
- await proxclient.accessDomains(apiBaseUrl, settings.sslValidation);
+ await proxclient.accessDomains(apiBaseUrl, settings.sslValidation!);
} on proxclient.ProxmoxApiException catch (e) {
showDialog(
context: context,
}
}
- response?.sort((a, b) => a.realm.compareTo(b.realm));
+ response?.sort((a, b) => a!.realm!.compareTo(b!.realm!));
final selection = response?.singleWhere(
- (e) => e.realm == widget.userModel?.realm,
+ (e) => e!.realm == widget.userModel?.realm,
orElse: () => response?.first,
);
class ProxmoxProgressOverlay extends StatelessWidget {
const ProxmoxProgressOverlay({
- Key key,
- @required this.message,
+ Key? key,
+ required this.message,
}) : super(key: key);
final String message;
final proxclient.ProxmoxApiException exception;
const ProxmoxApiErrorDialog({
- Key key,
- @required this.exception,
+ Key? key,
+ required this.exception,
}) : super(key: key);
@override
class ProxmoxCertificateErrorDialog extends StatelessWidget {
const ProxmoxCertificateErrorDialog({
- Key key,
+ Key? key,
}) : super(key: key);
@override
abstract class ProxmoxLoginStorage
implements Built<ProxmoxLoginStorage, ProxmoxLoginStorageBuilder> {
- BuiltList<ProxmoxLoginModel> get logins;
+ BuiltList<ProxmoxLoginModel>? get logins;
ProxmoxLoginStorage._();
factory ProxmoxLoginStorage(
- [void Function(ProxmoxLoginStorageBuilder) updates]) =
+ [void Function(ProxmoxLoginStorageBuilder)? updates]) =
_$ProxmoxLoginStorage;
- Object toJson() {
+ Object? toJson() {
return serializers.serializeWith(ProxmoxLoginStorage.serializer, this);
}
- static ProxmoxLoginStorage fromJson(Object json) {
+ static ProxmoxLoginStorage? fromJson(Object? json) {
return serializers.deserializeWith(ProxmoxLoginStorage.serializer, json);
}
static Serializer<ProxmoxLoginStorage> get serializer =>
_$proxmoxLoginStorageSerializer;
- static Future<ProxmoxLoginStorage> fromLocalStorage() async {
+ static Future<ProxmoxLoginStorage?> fromLocalStorage() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('ProxmoxLoginList')) {
- final decodedJson = json.decode(prefs.getString('ProxmoxLoginList'));
+ final decodedJson = json.decode(prefs.getString('ProxmoxLoginList')!);
return fromJson(decodedJson);
}
return ProxmoxLoginStorage();
}
Future<proxclient.ProxmoxApiClient> recoverLatestSession() async {
- final latestSession = logins.singleWhere((e) => e.ticket.isNotEmpty);
+ final latestSession = logins!.singleWhere((e) => e.ticket!.isNotEmpty);
final settings = await ProxmoxGeneralSettingsModel.fromLocalStorage();
final apiClient = await proxclient.authenticate(latestSession.fullUsername,
- latestSession.ticket, latestSession.origin, settings.sslValidation);
+ latestSession.ticket!, latestSession.origin!, settings.sslValidation!);
return apiClient;
}
Future<void> invalidateAllSessions() async {
final invalidatedList =
- logins.map((e) => e.rebuild((login) => login..ticket = ''));
+ logins!.map((e) => e.rebuild((login) => login..ticket = ''));
await rebuild((e) => e.logins.replace(invalidatedList)).saveToDisk();
}
}
abstract class ProxmoxLoginModel
implements Built<ProxmoxLoginModel, ProxmoxLoginModelBuilder> {
- Uri get origin;
+ Uri? get origin;
- String get username;
+ String? get username;
- String get realm;
+ String? get realm;
- ProxmoxProductType get productType;
+ ProxmoxProductType? get productType;
- String get ticket;
+ String? get ticket;
/// The username with the corresponding realm e.g. root@pam
String get fullUsername => '$username@$realm';
bool get activeSession =>
- ticket != null && ticket.isNotEmpty && !ticketExpired();
+ ticket != null && ticket!.isNotEmpty && !ticketExpired();
- @nullable
- String get hostname;
+ String? get hostname;
String get fullHostname {
- if (origin.host == hostname) {
- return hostname;
+ var location = origin;
+ if (location == null) {
+ return 'Unknown';
}
- if (hostname == null || hostname.isEmpty) {
- return origin.host;
+ var name = hostname;
+ if (name == null || name.isEmpty) {
+ return location.host;
}
-
- return '${origin.host} - $hostname';
+ if (location.host == name) {
+ return name;
+ }
+ return '${location.host} - $hostname';
}
ProxmoxLoginModel._();
- factory ProxmoxLoginModel([void Function(ProxmoxLoginModelBuilder) updates]) =
- _$ProxmoxLoginModel;
+ factory ProxmoxLoginModel(
+ [void Function(ProxmoxLoginModelBuilder)? updates]) = _$ProxmoxLoginModel;
- Map<String, dynamic> toJson() {
- return serializers.serializeWith(ProxmoxLoginModel.serializer, this);
+ Map<String, dynamic>? toJson() {
+ return serializers.serializeWith(ProxmoxLoginModel.serializer, this)
+ as Map<String, dynamic>?;
}
- static ProxmoxLoginModel fromJson(Map<String, dynamic> json) {
+ static ProxmoxLoginModel? fromJson(Map<String, dynamic> json) {
return serializers.deserializeWith(ProxmoxLoginModel.serializer, json);
}
bool ticketExpired() {
final ticketRegex = RegExp(r'(PVE|PMG)(?:QUAR)?:(?:(\S+):)?([A-Z0-9]{8})::')
- .firstMatch(ticket);
+ .firstMatch(ticket!)!;
final time = DateTime.fromMillisecondsSinceEpoch(
- int.parse(ticketRegex.group(3), radix: 16) * 1000);
+ int.parse(ticketRegex.group(3)!, radix: 16) * 1000);
return DateTime.now().isAfter(time.add(Duration(hours: 1)));
}
}
import 'package:flutter/material.dart';
+import 'package:built_collection/built_collection.dart';
import 'package:proxmox_login_manager/proxmox_general_settings_form.dart';
import 'package:proxmox_login_manager/proxmox_login_form.dart';
import 'package:proxmox_login_manager/proxmox_login_model.dart';
typedef OnLoginCallback = Function(proxclient.ProxmoxApiClient client);
class ProxmoxLoginSelector extends StatefulWidget {
- final OnLoginCallback onLogin;
+ final OnLoginCallback? onLogin;
- const ProxmoxLoginSelector({Key key, this.onLogin}) : super(key: key);
+ const ProxmoxLoginSelector({Key? key, this.onLogin}) : super(key: key);
@override
_ProxmoxLoginSelectorState createState() => _ProxmoxLoginSelectorState();
}
class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
- Future<ProxmoxLoginStorage> loginStorage;
+ Future<ProxmoxLoginStorage?>? loginStorage;
@override
void initState() {
super.initState();
})
],
),
- body: FutureBuilder<ProxmoxLoginStorage>(
+ body: FutureBuilder<ProxmoxLoginStorage?>(
future: loginStorage,
builder: (context, snapshot) {
if (!snapshot.hasData) {
child: CircularProgressIndicator(),
);
}
- if (snapshot.hasData && (snapshot.data.logins?.isEmpty ?? true)) {
+ if (snapshot.hasData &&
+ (snapshot.data!.logins?.isEmpty ?? true)) {
return Center(
child: Text('Add an account'),
);
}
var items = <Widget>[];
- final logins = snapshot.data?.logins;
+ final BuiltList<ProxmoxLoginModel> logins =
+ snapshot.data?.logins ?? BuiltList<ProxmoxLoginModel>();
final activeSessions =
logins.rebuild((b) => b.where((b) => b.activeSession));
leading: Icon(Icons.logout),
title: Text('Logout'),
onTap: () async {
- await snapshot.data
+ await snapshot.data!
.rebuild((b) => b.logins
.rebuildWhere((m) => s == m,
(b) => b..ticket = ''))
),
),
),
- ...logins.where((b) => !b.activeSession)?.map((l) => ListTile(
+ ...logins.where((b) => !b.activeSession).map((l) => ListTile(
title: Text(l.fullHostname),
subtitle: Text(l.fullUsername),
trailing: Icon(Icons.navigate_next),
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () async {
- await snapshot.data
+ await snapshot.data!
.rebuild((b) => b.logins.remove(l))
.saveToDisk();
refreshFromStorage();
);
}
- Future<void> _login({ProxmoxLoginModel user, bool isCreate = false}) async {
+ Future<void> _login({ProxmoxLoginModel? user, bool isCreate = false}) async {
final client = await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProxmoxLoginPage(
userModel: user,
)));
refreshFromStorage();
if (client != null) {
- widget.onLogin(client);
+ widget.onLogin!(client);
}
}
import 'package:proxmox_login_manager/proxmox_login_form.dart';
class ProxmoxTfaForm extends StatefulWidget {
- final ProxmoxApiClient apiClient;
+ final ProxmoxApiClient? apiClient;
- const ProxmoxTfaForm({Key key, this.apiClient}) : super(key: key);
+ const ProxmoxTfaForm({Key? key, this.apiClient}) : super(key: key);
@override
_ProxmoxTfaFormState createState() => _ProxmoxTfaFormState();
});
try {
final client =
- await widget.apiClient.finishTfaChallenge(_codeController.text);
+ await widget.apiClient!.finishTfaChallenge(_codeController.text);
Navigator.of(context).pop(client);
} on ProxmoxApiException catch (e) {
showDialog(
author: Tim Marx <t.marx@proxmox.com>
environment:
- sdk: ">=2.7.0 <3.0.0"
+ sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter: