// FIXME: copied from pve_flutter_frontend, re-use common set
class ProxmoxColors {
- static final Color orange = Color(0xFFE57000);
- static final Color supportGrey = Color(0xFFABBABA);
- static final Color supportBlue = Color(0xFF00617F);
+ static const Color orange = Color(0xFFE57000);
+ static const Color supportGrey = Color(0xFFABBABA);
+ static const Color supportBlue = Color(0xFF00617F);
}
class ProxmoxLoginForm extends StatefulWidget {
final PveAccessDomainModel? selectedDomain;
final ValueChanged<PveAccessDomainModel?> onDomainChanged;
final Function? onPasswordSubmitted;
- final Function? onOriginSubmitted;
+ final Function onOriginSubmitted;
final Function? onSavePasswordChanged;
final bool? canSavePassword;
final bool? passwordSaved;
this.selectedDomain,
required this.onDomainChanged,
this.onPasswordSubmitted,
- this.onOriginSubmitted,
+ required this.onOriginSubmitted,
this.onSavePasswordChanged,
this.canSavePassword,
this.passwordSaved,
}) : super(key: key);
@override
- _ProxmoxLoginFormState createState() => _ProxmoxLoginFormState();
+ State<ProxmoxLoginForm> createState() => _ProxmoxLoginFormState();
}
class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
Widget build(BuildContext context) {
if (widget.accessDomains == null) {
return TextFormField(
- decoration: InputDecoration(
+ decoration: const InputDecoration(
icon: Icon(Icons.vpn_lock),
labelText: 'Origin',
hintText: 'e.g. 192.168.1.2',
helperText:
'Protocol (https) and default port (8006 or 443) implied'),
+ textInputAction: TextInputAction.next,
controller: widget.originController,
validator: widget.originValidator,
- onFieldSubmitted: (value) => widget.onOriginSubmitted!(),
+ onFieldSubmitted: (value) => widget.onOriginSubmitted(),
);
}
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
- decoration: InputDecoration(
+ decoration: const InputDecoration(
icon: Icon(Icons.vpn_lock),
labelText: 'Origin',
),
enabled: false,
),
TextFormField(
- decoration: InputDecoration(
+ decoration: const InputDecoration(
icon: Icon(Icons.person),
labelText: 'Username',
),
}
return null;
},
- autofillHints: [AutofillHints.username],
+ autofillHints: const [AutofillHints.username],
),
DropdownButtonFormField(
- decoration: InputDecoration(icon: Icon(Icons.domain)),
+ decoration: const InputDecoration(icon: Icon(Icons.domain)),
items: widget.accessDomains!
.map((e) => DropdownMenuItem(
+ value: e,
child: ListTile(
title: Text(e!.realm),
subtitle: Text(e.comment ?? ''),
),
- value: e,
))
.toList(),
onChanged: widget.onDomainChanged,
Stack(
children: [
TextFormField(
- decoration: InputDecoration(
+ decoration: const InputDecoration(
icon: Icon(Icons.lock),
labelText: 'Password',
),
return null;
},
onFieldSubmitted: (value) => widget.onPasswordSubmitted!(),
- autofillHints: [AutofillHints.password],
+ autofillHints: const [AutofillHints.password],
),
Align(
alignment: Alignment.bottomRight,
child: IconButton(
- constraints: BoxConstraints.tight(Size(58, 58)),
+ constraints: BoxConstraints.tight(const Size(58, 58)),
iconSize: 24,
icon:
Icon(_obscure ? Icons.visibility : Icons.visibility_off),
this.password,
}) : super(key: key);
@override
- _ProxmoxLoginPageState createState() => _ProxmoxLoginPageState();
+ State<ProxmoxLoginPage> createState() => _ProxmoxLoginPageState();
}
class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
disabledBackgroundColor: Colors.grey,
),
),
- toggleableActiveColor: ProxmoxColors.orange,
- colorScheme: ColorScheme.dark().copyWith(
+ colorScheme: const ColorScheme.dark().copyWith(
primary: ProxmoxColors.orange,
secondary: ProxmoxColors.orange,
onSecondary: ProxmoxColors.supportGrey),
+ checkboxTheme: CheckboxThemeData(
+ fillColor: MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return ProxmoxColors.orange;
+ }
+ return null;
+ }),
+ ),
+ radioTheme: RadioThemeData(
+ fillColor: MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return ProxmoxColors.orange;
+ }
+ return null;
+ }),
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor: MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return ProxmoxColors.orange;
+ }
+ return null;
+ }),
+ trackColor: MaterialStateProperty.resolveWith<Color?>(
+ (Set<MaterialState> states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return ProxmoxColors.orange;
+ }
+ return null;
+ }),
+ ),
),
child: Scaffold(
backgroundColor: ProxmoxColors.supportBlue,
elevation: 0.0,
backgroundColor: Colors.transparent,
leading: IconButton(
- icon: Icon(Icons.close),
+ icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
),
_selectedDomain = value;
});
},
- onOriginSubmitted: _submittButtonEnabled
- ? () {
- final isValid =
- _formKey.currentState!.validate();
- setState(() {
- _submittButtonEnabled = isValid;
- });
- if (isValid) {
- setState(() {
- _accessDomains =
- _getAccessDomains();
- });
- }
- }
- : null,
+ onOriginSubmitted: () {
+ final isValid =
+ _formKey.currentState!.validate();
+ setState(() {
+ _submittButtonEnabled = isValid;
+ });
+ if (isValid) {
+ setState(() {
+ _accessDomains = _getAccessDomains();
+ });
+ }
+ },
onPasswordSubmitted: _submittButtonEnabled
? () {
final isValid =
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
- child: Container(
+ child: SizedBox(
width: MediaQuery.of(context).size.width,
child: TextButton(
onPressed: _submittButtonEnabled
}
}
: null,
- child: Text('Continue'),
+ child: const Text('Continue'),
),
),
),
var client = await proxclient.authenticate(
'$username@$realm', password, origin, settings.sslValidation!);
- if (client.credentials.tfa != null) {
- client = await Navigator.of(context).push(MaterialPageRoute(
+ if (client.credentials.tfa != null &&
+ client.credentials.tfa!.kinds().isNotEmpty) {
+ if (!mounted) return;
+ ProxmoxApiClient? tfaclient =
+ await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProxmoxTfaForm(
apiClient: client,
),
));
+
+ if (tfaclient != null) {
+ client = tfaclient;
+ } else {
+ setState(() {
+ _progressModel.inProgress -= 1;
+ });
+ return;
+ }
}
final status = await client.getClusterStatus();
}
await loginStorage.saveToDisk();
- Navigator.of(context).pop(client);
+ if (mounted) {
+ Navigator.of(context).pop(client);
+ }
} on proxclient.ProxmoxApiException catch (e) {
print(e);
+ if (!mounted) return;
if (e.message.contains('No ticket')) {
showDialog(
context: context,
builder: (context) => AlertDialog(
- title: Text('Version Error'),
- content: Text(
+ title: const Text('Version Error'),
+ content: const Text(
'Proxmox VE version not supported, please update your instance to use this app.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
- child: Text('Close'),
+ child: const Text('Close'),
),
],
),
} catch (e, trace) {
print(e);
print(trace);
- if (e.runtimeType == HandshakeException) {
- showDialog(
- context: context,
- builder: (context) => ProxmoxCertificateErrorDialog(),
- );
- } else {
- showDialog(
- context: context,
- builder: (context) => ConnectionErrorDialog(exception: e),
- );
+ if (mounted) {
+ if (e.runtimeType == HandshakeException) {
+ showDialog(
+ context: context,
+ builder: (context) => const ProxmoxCertificateErrorDialog(),
+ );
+ } else {
+ showDialog(
+ context: context,
+ builder: (context) => ConnectionErrorDialog(exception: e),
+ );
+ }
}
}
setState(() {
try {
response = await proxclient.accessDomains(uri, settings.sslValidation!);
} on proxclient.ProxmoxApiException catch (e) {
- showDialog(
- context: context,
- builder: (context) => ProxmoxApiErrorDialog(
- exception: e,
- ),
- );
+ if (mounted) {
+ showDialog(
+ context: context,
+ builder: (context) => ProxmoxApiErrorDialog(
+ exception: e,
+ ),
+ );
+ }
} on HandshakeException {
- showDialog(
- context: context,
- builder: (context) => ProxmoxCertificateErrorDialog(),
- );
+ if (mounted) {
+ showDialog(
+ context: context,
+ builder: (context) => const ProxmoxCertificateErrorDialog(),
+ );
+ }
}
return response;
}
try {
response = await _loadAccessDomains(uri);
} catch (e) {
- showDialog(
- context: context,
- builder: (context) => ConnectionErrorDialog(
- exception: e,
- ),
- );
+ if (mounted) {
+ showDialog(
+ context: context,
+ builder: (context) => ConnectionErrorDialog(
+ exception: e,
+ ),
+ );
+ }
}
return response;
}
var host = _originController.text.trim();
var apiBaseUrl = normalizeUrl(host);
- RegExp portRE = new RegExp(r":\d{1,5}$");
+ RegExp portRE = RegExp(r":\d{1,5}$");
List<PveAccessDomainModel?>? response;
@override
Widget build(BuildContext context) {
return Container(
- decoration: new BoxDecoration(color: Colors.black.withOpacity(0.5)),
+ decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
message,
- style: TextStyle(
+ style: const TextStyle(
fontSize: 20,
),
),
- Padding(
- padding: const EdgeInsets.only(top: 20.0),
+ const Padding(
+ padding: EdgeInsets.only(top: 20.0),
child: CircularProgressIndicator(),
)
],
}
class ConnectionErrorDialog extends StatelessWidget {
- final exception;
+ final Object exception;
const ConnectionErrorDialog({
Key? key,
@override
Widget build(BuildContext context) {
return AlertDialog(
- title: Text('Connection error'),
- content: Text('Could not establish connection: ${this.exception}'),
+ title: const Text('Connection error'),
+ content: Text('Could not establish connection: $exception'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
- child: Text('Close'),
+ child: const Text('Close'),
),
],
);
@override
Widget build(BuildContext context) {
return AlertDialog(
- title: Text('API Error'),
+ title: const Text('API Error'),
content: SingleChildScrollView(
child: Text(exception.message),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
- child: Text('Close'),
+ child: const Text('Close'),
),
],
);
@override
Widget build(BuildContext context) {
return AlertDialog(
- title: Text('Certificate error'),
+ title: const Text('Certificate error'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text('Your connection is not private.'),
+ const Text('Your connection is not private.'),
Text(
'Note: Consider to disable SSL validation,'
' if you use a self signed, not commonly trusted, certificate.',
- style: Theme.of(context).textTheme.caption,
+ style: Theme.of(context).textTheme.bodySmall,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
- child: Text('Close'),
+ child: const Text('Close'),
),
TextButton(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(
- builder: (context) => ProxmoxGeneralSettingsForm())),
- child: Text('Settings'),
+ builder: (context) => const ProxmoxGeneralSettingsForm())),
+ child: const Text('Settings'),
)
],
);
urlText = urlText.substring('https://'.length);
}
if (urlText.startsWith('http://')) {
- throw new Exception("HTTP without TLS is not supported");
+ throw Exception("HTTP without TLS is not supported");
}
return Uri.https(urlText, '');