import 'package:pve_flutter_frontend/bloc/proxmox_base_bloc.dart';
import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
-class PveAuthenticationBloc extends ProxmoxBaseBloc<PveAuthenticationEvent,PveAuthenticationState>{
-
+class PveAuthenticationBloc
+ extends ProxmoxBaseBloc<PveAuthenticationEvent, PveAuthenticationState> {
@override
PveAuthenticationState get initialState => Unauthenticated();
yield Authenticated(event.apiClient);
}
if (event is LoggedOut) {
+ await storeTicket('');
yield Unauthenticated();
}
}
-
-
}
-abstract class PveAuthenticationEvent {
-}
+abstract class PveAuthenticationEvent {}
class AppStarted extends PveAuthenticationEvent {
@override
String toString() => 'LoggedOut';
}
-abstract class PveAuthenticationState{
-}
+abstract class PveAuthenticationState {}
class Uninitialized extends PveAuthenticationState {
@override
class Unauthenticated extends PveAuthenticationState {
@override
String toString() => 'Unauthenticated';
-}
\ No newline at end of file
+}
import 'dart:async';
+import 'package:meta/meta.dart';
import 'package:pve_flutter_frontend/bloc/proxmox_base_bloc.dart';
import 'package:pve_flutter_frontend/events/pve_login_events.dart';
-import 'package:pve_flutter_frontend/states/pve_login_states.dart';
-import 'package:pve_flutter_frontend/utils/validators.dart';
-import 'package:rxdart/rxdart.dart';
+import 'package:pve_flutter_frontend/states/pve_login_state.dart';
import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'
as proxclient;
+import 'package:pve_flutter_frontend/utils/validators.dart';
class PveLoginBloc extends ProxmoxBaseBloc<PveLoginEvent, PveLoginState> {
- PveLoginState get initialState => PveLoginState.empty();
+ final PveLoginState init;
+
+ PveLoginBloc({@required this.init});
+ PveLoginState get initialState => init;
@override
Stream<PveLoginState> processEvents(PveLoginEvent event) async* {
+ yield latestState.rebuild((b) => b..errorMessage = "");
+
if (event is UsernameChanged) {
yield* _mapUsernameChangedToState(event.username);
} else if (event is PasswordChanged) {
password: event.password,
hostname: event.origin);
}
+
+ if (event is LoadOrigin) {
+ final origin = await proxclient.getPlatformAwareOrigin();
+ yield latestState.rebuild((b) => b
+ ..origin = origin ?? ''
+ ..isBlank = false);
+ }
}
Stream<PveLoginState> _mapUsernameChangedToState(String username) async* {
//TODO implement username validator?
- yield latestState.rebuild((b) => b
- ..isUsernameValid = true
- ..errorMessage = "");
}
Stream<PveLoginState> _mapPasswordChangedToState(String password) async* {
//TODO implement password validator?
- yield latestState.rebuild((b) => b
- ..isPasswordValid = true
- ..errorMessage = "");
}
Stream<PveLoginState> _mapOriginChangedToState(String origin) async* {
- proxclient.storePlatformAwareOrigin(origin);
-//TODO implement origin validator?
- yield latestState.rebuild((b) => b..errorMessage = "");
+ if (origin.isNotEmpty) {
+ try {
+ var uri = Uri.parse(origin);
+ print(uri.pathSegments.length);
+ if (uri.pathSegments.length < 3 && Validators.isValidDnsName(origin)) {
+ uri = uri.replace(host: origin);
+ }
+ if (!uri.hasPort) {
+ uri = uri.replace(port: 8006);
+ }
+ if (!uri.hasScheme) {
+ uri = uri.replace(scheme: 'https');
+ }
+ print(uri.origin);
+ yield latestState.rebuild((b) => b
+ ..originFieldError = ''
+ ..origin = uri.origin);
+ await proxclient.storePlatformAwareOrigin(uri.origin);
+ } on StateError catch (e) {
+ yield latestState.rebuild((b) => b..originFieldError = e.message);
+ } catch (e) {
+ yield latestState
+ .rebuild((b) => b..originFieldError = 'Please check input');
+ }
+ }
}
Stream<PveLoginState> _mapLoginWithCredentialsPressedToState(
{String username, String password, String hostname}) async* {
- yield PveLoginState.loading();
+ yield latestState.rebuild((b) => b..isLoading = true);
try {
final client = await proxclient.authenticate(username, password);
- yield PveLoginState.success(apiClient: client);
+
+ yield latestState.rebuild((b) => b
+ ..apiClient = client
+ ..isLoading = false
+ ..isSuccess = true);
} on proxclient.ProxmoxApiException catch (e) {
- yield PveLoginState.failure(e.message);
+ yield latestState.rebuild((b) => b
+ ..errorMessage = e.message
+ ..isLoading = false);
} catch (e, trace) {
+ yield latestState.rebuild((b) => b
+ ..errorMessage = e.toString()
+ ..isLoading = false);
print(e);
print(trace);
- yield PveLoginState.failure(e.toString());
}
}
}
final String password;
final String origin;
- LoginWithCredentialsPressed({@required this.username, @required this.password, this.origin});
+ LoginWithCredentialsPressed(
+ {@required this.username, @required this.password, this.origin});
@override
String toString() {
return 'LoginWithCredentialsPressed { email: $username, password: $password, hostname: $origin }';
}
-}
\ No newline at end of file
+}
+
+class LoadOrigin extends PveLoginEvent {}
import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart';
import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart';
import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart';
+import 'package:pve_flutter_frontend/events/pve_login_events.dart';
import 'package:pve_flutter_frontend/pages/404_page.dart';
import 'package:pve_flutter_frontend/pages/login_page.dart';
import 'package:pve_flutter_frontend/pages/main_layout_slim.dart';
import 'package:pve_flutter_frontend/pages/main_layout_wide.dart';
import 'package:pve_flutter_frontend/states/pve_cluster_status_state.dart';
+import 'package:pve_flutter_frontend/states/pve_login_state.dart';
import 'package:pve_flutter_frontend/states/pve_lxc_overview_state.dart';
import 'package:pve_flutter_frontend/states/pve_node_overview_state.dart';
import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart';
scaffoldBackgroundColor: Colors.white,
),
onGenerateRoute: (context) {
- if (authbloc.state.value is Unauthenticated) {
+ if (authbloc.state.value is Unauthenticated ||
+ context.name == '/login') {
return MaterialPageRoute(
builder: (context) {
- return PveLoginPage(
- loginBloc: PveLoginBloc(),
- authenticationBloc: authbloc,
+ return MultiProvider(
+ providers: [
+ Provider<PveLoginBloc>(
+ create: (context) =>
+ PveLoginBloc(init: PveLoginState.init(''))
+ ..events.add(LoadOrigin()),
+ dispose: (context, bloc) => bloc.dispose(),
+ ),
+ Provider.value(
+ value: authbloc,
+ )
+ ],
+ child: PveLoginPage(),
);
},
);
);
}
switch (context.name) {
+ case '/':
+ return MaterialPageRoute(
+ fullscreenDialog: true,
+ settings: context,
+ builder: (context) => MultiProvider(
+ providers: [
+ Provider<proxclient.ProxmoxApiClient>.value(
+ value: state.apiClient,
+ ),
+ Provider<PveClusterStatusBloc>(
+ create: (context) => PveClusterStatusBloc(
+ apiClient: state.apiClient,
+ init: PveClusterStatusState.init())
+ ..events.add(UpdateClusterStatus()),
+ dispose: (context, bloc) => bloc.dispose(),
+ )
+ ],
+ child: ProxmoxLayoutBuilder(
+ builder: (context, layout) => layout != ProxmoxLayout.slim
+ ? MainLayoutWide()
+ : MainLayoutSlim(),
+ ),
+ ),
+ );
+ break;
case PveCreateVmWizard.routeName:
return MaterialPageRoute(
fullscreenDialog: true,
value: state.apiClient, child: PveCreateVmWizard());
},
);
+ break;
+
case PveConsoleWidget.routeName:
return MaterialPageRoute(
fullscreenDialog: true,
));
},
);
+ break;
+
default:
return MaterialPageRoute(
settings: context,
}
}
},
- home: RootPage(),
+ initialRoute: '/',
),
);
}
}
-
-class RootPage extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- final authBloc = Provider.of<PveAuthenticationBloc>(context);
- return StreamBuilder<PveAuthenticationState>(
- stream: authBloc.state,
- builder: (context, snapshot) {
- if (snapshot.hasData) {
- final state = snapshot.data;
-
- if (state is Unauthenticated) {
- return PveLoginPage(
- loginBloc: PveLoginBloc(),
- authenticationBloc: authBloc,
- );
- }
- if (state is Authenticated) {
- return MultiProvider(
- providers: [
- Provider<proxclient.ProxmoxApiClient>.value(
- value: state.apiClient,
- ),
- Provider<PveClusterStatusBloc>(
- create: (context) => PveClusterStatusBloc(
- apiClient: state.apiClient,
- init: PveClusterStatusState.init())
- ..events.add(UpdateClusterStatus()),
- dispose: (context, bloc) => bloc.dispose(),
- )
- ],
- child: ProxmoxLayoutBuilder(
- builder: (context, layout) => layout != ProxmoxLayout.slim
- ? MainLayoutWide()
- : MainLayoutSlim(),
- ),
- );
- }
-
- if (state is Uninitialized) {
- return Container();
- }
- }
- return Container();
- });
- }
-}
import 'package:flutter/material.dart';
-import 'package:pve_flutter_frontend/bloc/pve_authentication_bloc.dart';
+import 'package:provider/provider.dart';
import 'package:pve_flutter_frontend/bloc/pve_login_bloc.dart';
-import 'package:pve_flutter_frontend/widgets/pve_login_form.dart';
-
-class PveLoginPage extends StatefulWidget {
- final PveLoginBloc loginBloc;
- final PveAuthenticationBloc authenticationBloc;
- static const routeName = '/login';
-
- PveLoginPage({
- Key key,
- @required this.loginBloc,
- @required this.authenticationBloc,
- }) : super(key: key);
-
- @override
- State<PveLoginPage> createState() => _PveLoginPageState();
-}
-
-class _PveLoginPageState extends State<PveLoginPage> {
- PveLoginBloc get _loginBloc => widget.loginBloc;
- PveAuthenticationBloc get _authenticationBloc => widget.authenticationBloc;
+import 'package:pve_flutter_frontend/states/pve_login_state.dart';
+import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart';
- @override
- void initState() {
- super.initState();
- _loginBloc.state.where((state) => state.isSuccess).forEach(
- (loginSucceded) => _authenticationBloc.events.add(
- LoggedIn(loginSucceded.apiClient)));
- }
+import 'package:pve_flutter_frontend/widgets/pve_login_form.dart';
+class PveLoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final lBloc = Provider.of<PveLoginBloc>(context);
+
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
- child: PveLoginForm(
- loginBloc: _loginBloc,
+ child: ProxmoxStreamBuilder<PveLoginBloc, PveLoginState>(
+ bloc: lBloc,
+ builder: (context, state) {
+ if (state.isBlank) {
+ return Center(
+ child: CircularProgressIndicator(),
+ );
+ }
+ return PveLoginForm(
+ savedOrigin: state.origin,
+ );
+ },
),
),
),
),
);
}
-
- @override
- void dispose() {
- _loginBloc.dispose();
- super.dispose();
- }
}
import 'package:flutter/material.dart';
import 'package:pve_flutter_frontend/bloc/pve_login_bloc.dart';
import 'package:pve_flutter_frontend/events/pve_login_events.dart';
-import 'package:pve_flutter_frontend/states/pve_login_states.dart';
+import 'package:pve_flutter_frontend/states/pve_login_state.dart';
+import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart';
+import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart';
+import 'package:provider/provider.dart';
+import 'package:pve_flutter_frontend/bloc/pve_authentication_bloc.dart';
class PveLoginForm extends StatefulWidget {
- final PveLoginBloc loginBloc;
-
+ final String savedOrigin;
PveLoginForm({
Key key,
- @required this.loginBloc,
+ this.savedOrigin,
}) : super(key: key);
@override
final _originController = TextEditingController();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
-
- PveLoginBloc get _loginBloc => widget.loginBloc;
-
+ PveLoginBloc lBloc;
@override
void initState() {
super.initState();
+ lBloc = Provider.of<PveLoginBloc>(context, listen: false);
_passwordController.addListener(_onPasswordChanged);
_usernameController.addListener(_onUsernameChanged);
_originController.addListener(_onOriginChanged);
- }
-
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- _loginBloc.state.where((state) => state.isFailure).listen(
- (state) => Scaffold.of(context).showSnackBar(
- SnackBar(
- content: Text(
- state.errorMessage ?? "Error",
- style: ThemeData.dark().textTheme.button,
- ),
- backgroundColor: ThemeData.dark().errorColor,
- behavior: SnackBarBehavior.floating,
- ),
- ),
- );
+ _originController.text = widget.savedOrigin;
}
@override
Widget build(BuildContext context) {
- return StreamBuilder<PveLoginState>(
- stream: _loginBloc.state,
- initialData: PveLoginState.empty(),
- builder: (BuildContext context, AsyncSnapshot<PveLoginState> snapshot) {
- if (snapshot.hasData) {
- final state = snapshot.data;
- return Theme(
- data: ThemeData.dark().copyWith(accentColor: Color(0xFFE47225)),
- child: Form(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Image.asset(
- 'assets/images/Proxmox_logo_white_orange_800.png'),
- SizedBox(height: 20),
- //TODO change this when there's a more official way to determine web e.g. Platform.isWeb or similar
- if (!kIsWeb)
+ final aBloc = Provider.of<PveAuthenticationBloc>(context);
+ return StreamListener(
+ stream: lBloc.state.where((state) => state.isSuccess),
+ onStateChange: (newState) {
+ aBloc.events.add(LoggedIn(newState.apiClient));
+ },
+ child: StreamListener(
+ stream: aBloc.state.where((state) => state is Authenticated),
+ onStateChange: (newState) => Navigator.of(context).pushNamed('/'),
+ child: ProxmoxStreamBuilder<PveLoginBloc, PveLoginState>(
+ errorHandler: false,
+ bloc: lBloc,
+ builder: (context, state) {
+ return Theme(
+ data: ThemeData.dark().copyWith(accentColor: Color(0xFFE47225)),
+ child: Form(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset(
+ 'assets/images/Proxmox_logo_white_orange_800.png'),
+ SizedBox(height: 20),
+ //TODO change this when there's a more official way to determine web e.g. Platform.isWeb or similar
+ if (!kIsWeb)
+ TextFormField(
+ decoration: InputDecoration(
+ icon: Icon(Icons.domain),
+ labelText: 'Origin',
+ hintText: 'e.g. https://ip-of-your-pve-host:8006'),
+ controller: _originController,
+ autovalidate: true,
+ validator: (v) =>
+ state.isOriginValid ? null : state.originFieldError,
+ ),
+ TextFormField(
+ decoration: InputDecoration(
+ icon: Icon(Icons.person),
+ labelText: 'Username',
+ ),
+ controller: _usernameController,
+ autovalidate: true,
+ validator: (v) => state.isUsernameValid
+ ? null
+ : state.userNameFieldError,
+ ),
TextFormField(
decoration: InputDecoration(
- icon: Icon(Icons.domain),
- labelText: 'Origin',
- hintText: 'e.g. https://ip-of-your-pve-host:8006'),
- controller: _originController,
+ icon: Icon(Icons.lock),
+ labelText: 'Password',
+ ),
+ controller: _passwordController,
+ obscureText: true,
+ autovalidate: true,
+ autocorrect: false,
+ validator: (v) => state.isPasswordValid
+ ? null
+ : state.passwordFieldError,
+ onFieldSubmitted: (text) => isLoginButtonEnabled(state)
+ ? _onLoginButtonPressed()
+ : null,
),
- TextFormField(
- decoration: InputDecoration(
- icon: Icon(Icons.person),
- labelText: 'Username',
+ SizedBox(height: 20),
+ RaisedButton(
+ onPressed: isLoginButtonEnabled(state)
+ ? _onLoginButtonPressed
+ : null,
+ color: Color(0xFFE47225),
+ child: Text('Login'),
),
- controller: _usernameController,
- ),
- TextFormField(
- decoration: InputDecoration(
- icon: Icon(Icons.lock),
- labelText: 'Password',
+ Container(
+ child:
+ state.isLoading ? CircularProgressIndicator() : null,
),
- controller: _passwordController,
- obscureText: true,
- autovalidate: true,
- autocorrect: false,
- validator: (_) {
- return !state.isPasswordValid ? 'Invalid Password' : null;
- },
- onFieldSubmitted: (text) => isLoginButtonEnabled(state)
- ? _onLoginButtonPressed()
- : null,
- ),
- SizedBox(height: 20),
- RaisedButton(
- onPressed: isLoginButtonEnabled(state)
- ? _onLoginButtonPressed
- : null,
- color: Color(0xFFE47225),
- child: Text('Login'),
- ),
- Container(
- child:
- state.isSubmitting ? CircularProgressIndicator() : null,
- ),
- ],
+ ],
+ ),
),
- ),
- );
- } else {
- return Container();
- }
- },
+ );
+ },
+ ),
+ ),
);
}
_passwordController.text.isNotEmpty;
bool isLoginButtonEnabled(PveLoginState state) {
- return state.isFormValid && isPopulated && !state.isSubmitting;
+ return state.isFormValid && isPopulated && !state.isLoading;
}
void _onUsernameChanged() {
}
void _onPasswordChanged() {
- _loginBloc.events.add(
+ lBloc.events.add(
PasswordChanged(password: _passwordController.text),
);
}
void _onOriginChanged() {
- _loginBloc.events.add(
+ lBloc.events.add(
OriginChanged(origin: _originController.text),
);
}
_onLoginButtonPressed() {
- _loginBloc.events.add(LoginWithCredentialsPressed(
+ lBloc.events.add(LoginWithCredentialsPressed(
username: _usernameController.text.trim() + "@pam",
password: _passwordController.text,
origin: _originController.text.trim()));