]>
Commit | Line | Data |
---|---|---|
49dcddb1 TM |
1 | import 'package:flutter/material.dart'; |
2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | |
3 | import 'package:provider/provider.dart'; | |
20827556 | 4 | import 'package:pve_flutter_frontend/bloc/pve_file_selector_bloc.dart'; |
a7114f44 TM |
5 | import 'package:pve_flutter_frontend/bloc/pve_migrate_bloc.dart'; |
6 | import 'package:pve_flutter_frontend/bloc/pve_node_selector_bloc.dart'; | |
49dcddb1 TM |
7 | import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart'; |
8 | import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart'; | |
20827556 | 9 | import 'package:pve_flutter_frontend/bloc/pve_storage_selector_bloc.dart'; |
49dcddb1 TM |
10 | import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart'; |
11 | import 'package:pve_flutter_frontend/bloc/pve_task_log_viewer_bloc.dart'; | |
12 | import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'; | |
20827556 | 13 | import 'package:pve_flutter_frontend/states/pve_file_selector_state.dart'; |
a7114f44 TM |
14 | import 'package:pve_flutter_frontend/states/pve_migrate_state.dart'; |
15 | import 'package:pve_flutter_frontend/states/pve_node_selector_state.dart'; | |
49dcddb1 TM |
16 | import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart'; |
17 | import 'package:pve_flutter_frontend/states/pve_resource_state.dart'; | |
20827556 | 18 | import 'package:pve_flutter_frontend/states/pve_storage_selector_state.dart'; |
49dcddb1 TM |
19 | import 'package:pve_flutter_frontend/states/pve_task_log_state.dart'; |
20 | import 'package:pve_flutter_frontend/states/pve_task_log_viewer_state.dart'; | |
691d9883 | 21 | import 'package:pve_flutter_frontend/utils/utils.dart'; |
49dcddb1 TM |
22 | import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart'; |
23 | import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart'; | |
52d45875 | 24 | import 'package:pve_flutter_frontend/widgets/pve_action_card_widget.dart'; |
20827556 | 25 | import 'package:pve_flutter_frontend/widgets/pve_guest_backup_widget.dart'; |
49dcddb1 | 26 | import 'package:pve_flutter_frontend/widgets/pve_guest_overview_header.dart'; |
a7114f44 | 27 | import 'package:pve_flutter_frontend/widgets/pve_guest_migrate_widget.dart'; |
a4965d42 | 28 | import 'package:pve_flutter_frontend/widgets/pve_qemu_options_widget.dart'; |
52d45875 | 29 | import 'package:pve_flutter_frontend/widgets/pve_qemu_power_settings_widget.dart'; |
2e4fe55a | 30 | import 'package:pve_flutter_frontend/widgets/pve_resource_data_card_widget.dart'; |
49dcddb1 TM |
31 | import 'package:pve_flutter_frontend/widgets/pve_task_log_expansiontile_widget.dart'; |
32 | import 'package:pve_flutter_frontend/widgets/pve_task_log_widget.dart'; | |
33 | ||
34 | class PveQemuOverview extends StatelessWidget { | |
35 | static final routeName = RegExp(r"\/nodes\/(\S+)\/qemu\/(\d+)"); | |
36 | final String guestID; | |
37 | ||
59df1bb3 | 38 | const PveQemuOverview({super.key, required this.guestID}); |
49dcddb1 | 39 | |
a1ceb38d TL |
40 | ActionCard createActionCard(String title, IconData icon, Function onTap) { |
41 | return ActionCard( | |
42 | icon: Icon( | |
43 | icon, | |
44 | size: 55, | |
45 | color: Colors.white24, | |
46 | ), | |
47 | title: title, | |
48 | onTap: onTap, | |
49 | ); | |
50 | } | |
51 | ||
49dcddb1 TM |
52 | @override |
53 | Widget build(BuildContext context) { | |
54 | final bloc = Provider.of<PveQemuOverviewBloc>(context); | |
4134a8f3 | 55 | final rBloc = Provider.of<PveResourceBloc>(context); |
49dcddb1 TM |
56 | final taskBloc = Provider.of<PveTaskLogBloc>(context); |
57 | final width = MediaQuery.of(context).size.width; | |
58 | return StreamListener<PveResourceState>( | |
4134a8f3 | 59 | stream: rBloc.state, |
49dcddb1 TM |
60 | onStateChange: (globalResourceState) { |
61 | final guest = globalResourceState.resourceByID('qemu/$guestID'); | |
62 | if (guest.node != bloc.latestState.nodeID) { | |
63 | bloc.events.add(Migration(false, guest.node)); | |
64 | } | |
65 | }, | |
66 | child: ProxmoxStreamBuilder<PveQemuOverviewBloc, PveQemuOverviewState>( | |
67 | bloc: bloc, | |
68 | builder: (context, state) { | |
69 | final status = state.currentStatus; | |
70 | final config = state.config; | |
2e4fe55a | 71 | final rrdData = state.rrdData; |
49dcddb1 | 72 | |
e693e20c AL |
73 | return SafeArea( |
74 | child: Scaffold( | |
75 | appBar: AppBar( | |
76 | //backgroundColor: Colors.transparent, | |
77 | elevation: 0, | |
78 | title: Text(config?.name ?? 'VM $guestID'), | |
79 | ), | |
80 | backgroundColor: Theme.of(context).colorScheme.background, | |
81 | body: SingleChildScrollView( | |
82 | child: Column( | |
83 | children: <Widget>[ | |
84 | PveGuestOverviewHeader( | |
85 | background: !(status?.template ?? false) | |
86 | ? PveGuestHeaderRRDPageView( | |
87 | rrdData: rrdData, | |
afc1680b | 88 | ) |
5d2e7931 | 89 | : const Center( |
e693e20c AL |
90 | child: Text( |
91 | "TEMPLATE", | |
92 | style: TextStyle( | |
93 | color: Colors.white, | |
691d9883 | 94 | ), |
e693e20c AL |
95 | ), |
96 | ), | |
97 | width: width, | |
98 | guestID: guestID, | |
99 | guestStatus: status?.getQemuStatus(), | |
100 | guestName: config?.name ?? 'VM $guestID', | |
101 | guestNodeID: state.nodeID, | |
102 | guestType: 'qemu', | |
103 | ha: status?.ha, | |
104 | template: status?.template ?? false, | |
105 | ), | |
106 | ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>( | |
107 | bloc: taskBloc, | |
108 | builder: (context, taskState) { | |
a3439f8c | 109 | if (taskState.tasks.isNotEmpty) { |
e693e20c AL |
110 | return PveTaskExpansionTile( |
111 | headerColor: | |
112 | Theme.of(context).colorScheme.onBackground, | |
113 | task: taskState.tasks.first, | |
114 | showMorePage: Provider<PveTaskLogBloc>( | |
115 | create: (context) => PveTaskLogBloc( | |
116 | apiClient: taskBloc.apiClient, | |
117 | init: PveTaskLogState.init(state.nodeID), | |
afc1680b | 118 | ) |
e693e20c AL |
119 | ..events.add( |
120 | FilterTasksByGuestID( | |
121 | guestID: guestID, | |
122 | ), | |
123 | ) | |
124 | ..events.add(LoadTasks()), | |
125 | dispose: (context, bloc) => bloc.dispose(), | |
4dc5fbe2 | 126 | child: const PveTaskLog(), |
e693e20c AL |
127 | ), |
128 | ); | |
129 | } | |
130 | return Container(); | |
131 | }, | |
132 | ), | |
27a2bb4e | 133 | SizedBox( |
e693e20c AL |
134 | height: 130, |
135 | child: SingleChildScrollView( | |
136 | scrollDirection: Axis.horizontal, | |
137 | child: Row( | |
138 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
139 | children: <Widget>[ | |
140 | if (!(status?.template ?? false)) | |
141 | createActionCard( | |
142 | 'Power Settings', | |
143 | Icons.power_settings_new, | |
144 | () => showPowerMenuBottomSheet( | |
145 | context, bloc)), | |
146 | if (!(status?.template ?? false)) | |
147 | createActionCard( | |
148 | 'Console', | |
149 | Icons.queue_play_next, | |
150 | () => showConsoleMenuBottomSheet( | |
151 | context, | |
152 | bloc.apiClient, | |
153 | guestID, | |
154 | state.nodeID, | |
155 | 'qemu', | |
156 | allowSpice: status?.spice ?? false, | |
157 | )), | |
a1ceb38d | 158 | createActionCard( |
e693e20c AL |
159 | 'Options', |
160 | Icons.settings, | |
161 | () => Navigator.of(context) | |
162 | .push(_createOptionsRoute(bloc))), | |
163 | if (!rBloc.latestState.isStandalone) | |
164 | createActionCard( | |
165 | 'Migrate', | |
166 | FontAwesomeIcons.paperPlane, | |
167 | () => Navigator.of(context).push( | |
168 | _createMigrationRoute(guestID, | |
169 | state.nodeID, bloc.apiClient))), | |
a1ceb38d | 170 | createActionCard( |
e693e20c AL |
171 | 'Backup', |
172 | FontAwesomeIcons.save, | |
a1ceb38d | 173 | () => Navigator.of(context).push( |
e693e20c AL |
174 | _createBackupRoute(guestID, state.nodeID, |
175 | bloc.apiClient))), | |
176 | ], | |
177 | ), | |
2e4fe55a | 178 | ), |
49dcddb1 | 179 | ), |
e693e20c AL |
180 | if (config != null) |
181 | PveResourceDataCardWidget( | |
182 | expandable: false, | |
5d2e7931 | 183 | title: const Text( |
e693e20c AL |
184 | 'Hardware', |
185 | style: TextStyle( | |
186 | fontWeight: FontWeight.bold, | |
187 | fontSize: 20, | |
188 | ), | |
afc1680b | 189 | ), |
e693e20c | 190 | children: [ |
2e4fe55a | 191 | ListTile( |
5d2e7931 | 192 | leading: const Icon(FontAwesomeIcons.memory), |
e693e20c | 193 | title: Text('${config.memory}'), |
5d2e7931 | 194 | subtitle: const Text('Memory'), |
2e4fe55a TM |
195 | dense: true, |
196 | ), | |
197 | ListTile( | |
5d2e7931 | 198 | leading: const Icon(Icons.memory), |
e693e20c AL |
199 | title: Text( |
200 | '${config.cores} Cores ${config.sockets} Socket'), | |
5d2e7931 | 201 | subtitle: const Text('Processor'), |
2e4fe55a | 202 | dense: true, |
2e4fe55a TM |
203 | ), |
204 | ListTile( | |
5d2e7931 | 205 | leading: const Icon(FontAwesomeIcons.microchip), |
e693e20c AL |
206 | title: Text( |
207 | config.bios?.name ?? 'Default (SeaBIOS)'), | |
5d2e7931 | 208 | subtitle: const Text('BIOS'), |
2e4fe55a | 209 | dense: true, |
e693e20c AL |
210 | ), |
211 | ListTile( | |
5d2e7931 | 212 | leading: const Icon(FontAwesomeIcons.cogs), |
e693e20c AL |
213 | dense: true, |
214 | title: | |
215 | Text(config.machine ?? 'Default (i440fx)'), | |
5d2e7931 | 216 | subtitle: const Text('Machine Type'), |
e693e20c AL |
217 | ), |
218 | ListTile( | |
5d2e7931 | 219 | leading: const Icon(FontAwesomeIcons.database), |
e693e20c AL |
220 | title: Text( |
221 | config.scsihw?.name ?? 'Default (i440fx)'), | |
5d2e7931 | 222 | subtitle: const Text('SCSI Controller'), |
e693e20c AL |
223 | dense: true, |
224 | ), | |
225 | for (var ide in config.ide!) | |
226 | ListTile( | |
d6b19533 TL |
227 | leading: |
228 | const Icon(FontAwesomeIcons.compactDisc), | |
e693e20c | 229 | title: Text(ide), |
5d2e7931 | 230 | subtitle: const Text('CD/DVD Drive'), |
e693e20c AL |
231 | dense: true, |
232 | ), | |
233 | for (var scsi in config.scsi!) | |
234 | ListTile( | |
5d2e7931 | 235 | leading: const Icon(FontAwesomeIcons.hdd), |
e693e20c | 236 | title: Text(scsi), |
5d2e7931 | 237 | subtitle: const Text('Hard Disk'), |
e693e20c AL |
238 | dense: true, |
239 | ), | |
240 | for (var net in config.net!) | |
241 | ListTile( | |
d6b19533 TL |
242 | leading: |
243 | const Icon(FontAwesomeIcons.ethernet), | |
e693e20c | 244 | dense: true, |
5d2e7931 | 245 | subtitle: const Text('Network Device'), |
e693e20c AL |
246 | title: Text(net), |
247 | ) | |
248 | ]), | |
249 | ], | |
250 | ))), | |
251 | ); | |
49dcddb1 TM |
252 | }), |
253 | ); | |
254 | } | |
52d45875 | 255 | |
a4965d42 TM |
256 | Route _createOptionsRoute(PveQemuOverviewBloc bloc) { |
257 | return PageRouteBuilder( | |
258 | pageBuilder: (context, animation, secondaryAnimation) => Provider.value( | |
259 | value: bloc, | |
5b5b3f73 TM |
260 | child: PveQemuOptions( |
261 | guestID: guestID, | |
262 | ), | |
a4965d42 TM |
263 | ), |
264 | transitionsBuilder: (context, animation, secondaryAnimation, child) { | |
265 | return ScaleTransition( | |
266 | scale: Tween<double>( | |
267 | begin: 0.0, | |
268 | end: 1.0, | |
269 | ).animate( | |
270 | CurvedAnimation( | |
271 | parent: animation, | |
272 | curve: Curves.fastOutSlowIn, | |
273 | ), | |
274 | ), | |
275 | child: child, | |
276 | ); | |
277 | }, | |
278 | ); | |
279 | } | |
a7114f44 TM |
280 | |
281 | Route _createMigrationRoute( | |
282 | String guestID, | |
283 | String nodeID, | |
284 | ProxmoxApiClient client, | |
285 | ) { | |
286 | return PageRouteBuilder( | |
287 | pageBuilder: (context, animation, secondaryAnimation) => | |
288 | MultiProvider(providers: [ | |
289 | Provider<PveMigrateBloc>( | |
290 | create: (context) => PveMigrateBloc( | |
291 | guestID: guestID, | |
292 | apiClient: client, | |
293 | init: PveMigrateState.init(nodeID, 'qemu')) | |
294 | ..events.add(CheckMigratePreconditions()), | |
295 | dispose: (context, bloc) => bloc.dispose(), | |
296 | ), | |
297 | Provider( | |
298 | create: (context) => PveNodeSelectorBloc( | |
299 | apiClient: client, | |
300 | init: PveNodeSelectorState.init(onlineValidator: true) | |
301 | .rebuild((b) => b..disallowedNodes.replace([nodeID])), | |
302 | )..events.add(LoadNodesEvent()), | |
303 | dispose: (context, PveNodeSelectorBloc bloc) => bloc.dispose(), | |
304 | ), | |
305 | Provider( | |
306 | create: (context) => PveTaskLogViewerBloc( | |
307 | apiClient: client, | |
308 | init: PveTaskLogViewerState.init( | |
309 | nodeID, | |
310 | ), | |
311 | ), | |
312 | dispose: (context, PveTaskLogViewerBloc bloc) => bloc.dispose(), | |
313 | ) | |
4dc5fbe2 | 314 | ], child: const PveGuestMigrate()), |
a7114f44 TM |
315 | transitionsBuilder: (context, animation, secondaryAnimation, child) { |
316 | return ScaleTransition( | |
317 | scale: Tween<double>( | |
318 | begin: 0.0, | |
319 | end: 1.0, | |
320 | ).animate( | |
321 | CurvedAnimation( | |
322 | parent: animation, | |
323 | curve: Curves.fastOutSlowIn, | |
324 | ), | |
325 | ), | |
326 | child: child, | |
327 | ); | |
328 | }, | |
329 | ); | |
330 | } | |
20827556 TM |
331 | |
332 | Route _createBackupRoute( | |
333 | String guestID, | |
334 | String nodeID, | |
335 | ProxmoxApiClient client, | |
336 | ) { | |
337 | return MaterialPageRoute( | |
338 | builder: (context) => MultiProvider( | |
339 | providers: [ | |
340 | Provider( | |
341 | create: (context) => PveStorageSelectorBloc( | |
342 | apiClient: client, | |
343 | init: PveStorageSelectorState.init(nodeID: nodeID) | |
344 | .rebuild((b) => b | |
345 | ..content = PveStorageContentType.backup | |
346 | ..filterActive = true), | |
347 | )..events.add(LoadStoragesEvent()), | |
348 | dispose: (context, PveStorageSelectorBloc bloc) => bloc.dispose(), | |
349 | ), | |
350 | Provider( | |
351 | create: (context) => PveFileSelectorBloc( | |
352 | apiClient: client, | |
353 | init: PveFileSelectorState.init(nodeID: nodeID).rebuild((b) => b | |
c3e1ecf3 | 354 | ..guestID = int.parse(guestID) |
20827556 TM |
355 | ..fileType = PveStorageContentType.backup), |
356 | )..events.add(LoadStorageContent()), | |
357 | dispose: (context, PveFileSelectorBloc bloc) => bloc.dispose(), | |
358 | ) | |
359 | ], | |
360 | child: PveGuestBackupWidget( | |
361 | guestID: guestID, | |
362 | )), | |
363 | ); | |
364 | } | |
319e2dc6 | 365 | |
598a93b4 | 366 | Future<T?> showPowerMenuBottomSheet<T>( |
319e2dc6 TM |
367 | BuildContext context, PveQemuOverviewBloc qemuBloc) async { |
368 | return showModalBottomSheet( | |
5d2e7931 | 369 | shape: const RoundedRectangleBorder( |
319e2dc6 TM |
370 | borderRadius: BorderRadius.vertical(top: Radius.circular(10))), |
371 | context: context, | |
372 | builder: (context) => Provider.value( | |
373 | value: qemuBloc, | |
5d2e7931 | 374 | child: const PveQemuPowerSettings(), |
319e2dc6 TM |
375 | ), |
376 | ); | |
377 | } | |
49dcddb1 | 378 | } |