]> git.proxmox.com Git - flutter/pve_flutter_frontend.git/blame - lib/widgets/pve_qemu_overview.dart
tree-wide: prefer sized box for whitespace
[flutter/pve_flutter_frontend.git] / lib / widgets / pve_qemu_overview.dart
CommitLineData
49dcddb1
TM
1import 'package:flutter/material.dart';
2import 'package:font_awesome_flutter/font_awesome_flutter.dart';
3import 'package:provider/provider.dart';
20827556 4import 'package:pve_flutter_frontend/bloc/pve_file_selector_bloc.dart';
a7114f44
TM
5import 'package:pve_flutter_frontend/bloc/pve_migrate_bloc.dart';
6import 'package:pve_flutter_frontend/bloc/pve_node_selector_bloc.dart';
49dcddb1
TM
7import 'package:pve_flutter_frontend/bloc/pve_qemu_overview_bloc.dart';
8import 'package:pve_flutter_frontend/bloc/pve_resource_bloc.dart';
20827556 9import 'package:pve_flutter_frontend/bloc/pve_storage_selector_bloc.dart';
49dcddb1
TM
10import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart';
11import 'package:pve_flutter_frontend/bloc/pve_task_log_viewer_bloc.dart';
12import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
20827556 13import 'package:pve_flutter_frontend/states/pve_file_selector_state.dart';
a7114f44
TM
14import 'package:pve_flutter_frontend/states/pve_migrate_state.dart';
15import 'package:pve_flutter_frontend/states/pve_node_selector_state.dart';
49dcddb1
TM
16import 'package:pve_flutter_frontend/states/pve_qemu_overview_state.dart';
17import 'package:pve_flutter_frontend/states/pve_resource_state.dart';
20827556 18import 'package:pve_flutter_frontend/states/pve_storage_selector_state.dart';
49dcddb1
TM
19import 'package:pve_flutter_frontend/states/pve_task_log_state.dart';
20import 'package:pve_flutter_frontend/states/pve_task_log_viewer_state.dart';
691d9883 21import 'package:pve_flutter_frontend/utils/utils.dart';
49dcddb1
TM
22import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart';
23import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart';
52d45875 24import 'package:pve_flutter_frontend/widgets/pve_action_card_widget.dart';
20827556 25import 'package:pve_flutter_frontend/widgets/pve_guest_backup_widget.dart';
49dcddb1 26import 'package:pve_flutter_frontend/widgets/pve_guest_overview_header.dart';
a7114f44 27import 'package:pve_flutter_frontend/widgets/pve_guest_migrate_widget.dart';
a4965d42 28import 'package:pve_flutter_frontend/widgets/pve_qemu_options_widget.dart';
52d45875 29import 'package:pve_flutter_frontend/widgets/pve_qemu_power_settings_widget.dart';
2e4fe55a 30import 'package:pve_flutter_frontend/widgets/pve_resource_data_card_widget.dart';
49dcddb1
TM
31import 'package:pve_flutter_frontend/widgets/pve_task_log_expansiontile_widget.dart';
32import 'package:pve_flutter_frontend/widgets/pve_task_log_widget.dart';
33
34class 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}