1 import 'package:flutter/material.dart';
2 import 'package:provider/provider.dart';
3 import 'package:pve_flutter_frontend/bloc/pve_task_log_bloc.dart';
4 import 'package:pve_flutter_frontend/bloc/pve_task_log_viewer_bloc.dart';
5 import 'package:pve_flutter_frontend/states/pve_task_log_state.dart';
6 import 'package:pve_flutter_frontend/states/pve_task_log_viewer_state.dart';
7 import 'package:pve_flutter_frontend/widgets/proxmox_stream_builder_widget.dart';
8 import 'package:pve_flutter_frontend/widgets/proxmox_stream_listener.dart';
9 import 'package:pve_flutter_frontend/widgets/pve_task_log_expansiontile_widget.dart';
11 class PveTaskLog extends StatefulWidget {
12 PveTaskLog({Key? key}) : super(key: key);
15 _PveTaskLogState createState() => _PveTaskLogState();
18 class _PveTaskLogState extends State<PveTaskLog> {
19 final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
20 late TextEditingController _userFilterController;
21 late TextEditingController _typeFilterController;
25 final bloc = Provider.of<PveTaskLogBloc>(context, listen: false);
26 final PveTaskLogState state = bloc.latestState;
27 _userFilterController = TextEditingController.fromValue(
28 TextEditingValue(text: state.userFilter ?? ''));
29 _typeFilterController = TextEditingController.fromValue(
30 TextEditingValue(text: state.typeFilter ?? ''));
34 Widget build(BuildContext context) {
35 final bloc = Provider.of<PveTaskLogBloc>(context);
36 return ProxmoxStreamBuilder<PveTaskLogBloc, PveTaskLogState>(
38 builder: (context, state) {
39 if (state.tasks != null) {
45 icon: Icon(Icons.close),
46 onPressed: () => Navigator.of(context).pop(),
50 icon: Icon(Icons.more_vert),
52 _scaffoldKey.currentState?.openEndDrawer(),
58 padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0),
60 crossAxisAlignment: CrossAxisAlignment.start,
64 style: Theme.of(context).textTheme.headline5,
70 decoration: InputDecoration(
73 prefixIcon: Icon(Icons.person)),
74 onChanged: (newValue) {
75 bloc.events.add(FilterTasksByUser(newValue));
76 bloc.events.add(LoadTasks());
78 controller: _userFilterController,
84 decoration: InputDecoration(
87 prefixIcon: Icon(Icons.description)),
88 onChanged: (newValue) {
89 bloc.events.add(FilterTasksByType(newValue));
90 bloc.events.add(LoadTasks());
92 controller: _typeFilterController,
97 DropdownButtonFormField<String>(
98 decoration: InputDecoration(labelText: 'Source'),
100 icon: Icon(Icons.arrow_downward),
103 onChanged: (String? newValue) {
104 bloc.events.add(FilterTasksBySource(newValue));
105 bloc.events.add(LoadTasks());
111 ].map<DropdownMenuItem<String>>((String value) {
112 return DropdownMenuItem<String>(
114 child: Container(child: Text(value)),
122 builder: (FormFieldState<bool> formFieldState) => Row(
123 mainAxisAlignment: MainAxisAlignment.spaceBetween,
127 value: state.onlyErrors,
129 formFieldState.didChange(value);
130 bloc.events.add(FilterTasksByError());
131 bloc.events.add(LoadTasks());
141 body: NotificationListener<ScrollNotification>(
142 onNotification: (ScrollNotification scrollInfo) {
143 if (scrollInfo.metrics.pixels >=
144 (0.8 * scrollInfo.metrics.maxScrollExtent)) {
145 if (!state.isLoading) {
146 bloc.events.add(LoadMoreTasks());
151 child: state.tasks.isNotEmpty
153 itemCount: state.tasks.length,
154 itemBuilder: (context, index) => PveTaskExpansionTile(
155 task: state.tasks[index],
159 child: Text("No tasks found"),
171 class PveTaskLogScrollView extends StatefulWidget {
173 final Widget jobTitle;
175 const PveTaskLogScrollView({
178 required this.jobTitle,
179 }) : super(key: key);
181 _PveTaskLogScrollViewState createState() => _PveTaskLogScrollViewState();
184 class _PveTaskLogScrollViewState extends State<PveTaskLogScrollView> {
185 ScrollController _scrollController = new ScrollController();
195 Widget build(BuildContext context) {
196 return StreamListener<PveTaskLogViewerState>(
197 stream: Provider.of<PveTaskLogViewerBloc>(context).state.distinct(),
198 onStateChange: (newState) {
199 if (_scrollController.hasClients) {
203 child: ProxmoxStreamBuilder<PveTaskLogViewerBloc, PveTaskLogViewerState>(
204 bloc: Provider.of<PveTaskLogViewerBloc>(context),
205 builder: (context, state) {
206 var indicatorColor = Colors.teal.shade500;
207 var statusChipColor = Colors.teal.shade100;
208 if (state.status?.failed ?? false) {
209 indicatorColor = Colors.red;
210 statusChipColor = Colors.red.shade100;
213 height: MediaQuery.of(context).size.height * 0.5,
215 crossAxisAlignment: CrossAxisAlignment.start,
219 alignment: Alignment.center,
221 padding: const EdgeInsets.symmetric(vertical: 8.0),
222 child: Text("Loading log data.."),
225 if (!state.isBlank) ...[
227 padding: EdgeInsets.fromLTRB(0, 5, 0, 5),
229 alignment: Alignment.topCenter,
237 if (state.status != null)
239 leading: widget.icon,
240 title: AnimatedDefaultTextStyle(
241 style: Theme.of(context)
244 .copyWith(fontWeight: FontWeight.bold),
245 duration: kThemeChangeDuration,
246 child: widget.jobTitle,
250 state.status!.status.name,
251 style: TextStyle(color: indicatorColor),
253 backgroundColor: statusChipColor,
257 if (state.log != null)
260 padding: const EdgeInsets.all(14.0),
261 child: ListView.builder(
262 controller: _scrollController,
263 itemCount: state.log!.lines!.length,
264 itemBuilder: (context, index) {
266 index == state.log!.lines!.length - 1;
267 final errorLine = state
268 .log!.lines![index].lineText!
271 color: isLast || errorLine
275 padding: const EdgeInsets.all(8.0),
277 state.log!.lines![index].lineText!,
279 color: isLast || errorLine
298 void _scrollToBottom() {
299 if (_scrollController.hasClients) {
300 _scrollController.animateTo(_scrollController.position.maxScrollExtent,
301 duration: Duration(milliseconds: 500), curve: Curves.easeOut);