add endpoint for general stats
load build data to graph redesign top info tiles place add button on header folder restructure
This commit is contained in:
		
							
								
								
									
										100
									
								
								frontend/lib/components/dashboard/builds_chart.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								frontend/lib/components/dashboard/builds_chart.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
import 'package:fl_chart/fl_chart.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class BuildsChart extends StatefulWidget {
 | 
			
		||||
  const BuildsChart({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.nrbuilds,
 | 
			
		||||
    required this.nrfailedbuilds,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final int nrbuilds;
 | 
			
		||||
  final int nrfailedbuilds;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _BuildsChartState createState() => _BuildsChartState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _BuildsChartState extends State<BuildsChart> {
 | 
			
		||||
  int touchedIndex = -1;
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      height: 300,
 | 
			
		||||
      child: AspectRatio(
 | 
			
		||||
        aspectRatio: 1.3,
 | 
			
		||||
        child: Row(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            const SizedBox(
 | 
			
		||||
              height: 18,
 | 
			
		||||
            ),
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: AspectRatio(
 | 
			
		||||
                aspectRatio: 1,
 | 
			
		||||
                child: PieChart(
 | 
			
		||||
                  PieChartData(
 | 
			
		||||
                      pieTouchData: PieTouchData(
 | 
			
		||||
                          touchCallback: (pieTouchResponse, touchresponse) {
 | 
			
		||||
                        setState(() {
 | 
			
		||||
                          if (touchresponse?.touchedSection != null) {
 | 
			
		||||
                            touchedIndex = touchresponse!
 | 
			
		||||
                                .touchedSection!.touchedSectionIndex;
 | 
			
		||||
                          } else {
 | 
			
		||||
                            touchedIndex = -1;
 | 
			
		||||
                          }
 | 
			
		||||
                        });
 | 
			
		||||
                      }),
 | 
			
		||||
                      borderData: FlBorderData(
 | 
			
		||||
                        show: false,
 | 
			
		||||
                      ),
 | 
			
		||||
                      sectionsSpace: 0,
 | 
			
		||||
                      centerSpaceRadius: 40,
 | 
			
		||||
                      sections: showingSections()),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            const SizedBox(
 | 
			
		||||
              width: 28,
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<PieChartSectionData> showingSections() {
 | 
			
		||||
    return List.generate(2, (i) {
 | 
			
		||||
      final isTouched = i == touchedIndex;
 | 
			
		||||
      final fontSize = isTouched ? 25.0 : 16.0;
 | 
			
		||||
      final radius = isTouched ? 60.0 : 50.0;
 | 
			
		||||
      switch (i) {
 | 
			
		||||
        case 0:
 | 
			
		||||
          return PieChartSectionData(
 | 
			
		||||
            color: const Color(0xff760707),
 | 
			
		||||
            value: widget.nrfailedbuilds.toDouble(),
 | 
			
		||||
            title:
 | 
			
		||||
                "${(widget.nrfailedbuilds * 100 / widget.nrbuilds).toStringAsFixed(2)}%",
 | 
			
		||||
            radius: radius,
 | 
			
		||||
            titleStyle: TextStyle(
 | 
			
		||||
                fontSize: fontSize,
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
                color: const Color(0xffffffff)),
 | 
			
		||||
          );
 | 
			
		||||
        case 1:
 | 
			
		||||
          return PieChartSectionData(
 | 
			
		||||
            color: const Color(0xff0a7005),
 | 
			
		||||
            value: (widget.nrbuilds - widget.nrfailedbuilds).toDouble(),
 | 
			
		||||
            title:
 | 
			
		||||
                "${((widget.nrbuilds - widget.nrfailedbuilds) * 100 / widget.nrbuilds).toStringAsFixed(2)}%",
 | 
			
		||||
            radius: radius,
 | 
			
		||||
            titleStyle: TextStyle(
 | 
			
		||||
                fontSize: fontSize,
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
                color: const Color(0xffffffff)),
 | 
			
		||||
          );
 | 
			
		||||
        default:
 | 
			
		||||
          throw Error();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								frontend/lib/components/dashboard/chart_card.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								frontend/lib/components/dashboard/chart_card.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
 | 
			
		||||
class ChartCard extends StatelessWidget {
 | 
			
		||||
  const ChartCard({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.title,
 | 
			
		||||
    required this.color,
 | 
			
		||||
    required this.textRight,
 | 
			
		||||
    required this.subtitle,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final Color color;
 | 
			
		||||
  final String title, textRight, subtitle;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      margin: const EdgeInsets.only(top: defaultPadding),
 | 
			
		||||
      padding: const EdgeInsets.all(defaultPadding),
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
        border: Border.all(width: 2, color: primaryColor.withOpacity(0.15)),
 | 
			
		||||
        borderRadius: const BorderRadius.all(
 | 
			
		||||
          Radius.circular(defaultPadding),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          SizedBox(
 | 
			
		||||
              height: 20,
 | 
			
		||||
              width: 20,
 | 
			
		||||
              child: Container(
 | 
			
		||||
                color: color,
 | 
			
		||||
              )),
 | 
			
		||||
          Expanded(
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.symmetric(horizontal: defaultPadding),
 | 
			
		||||
              child: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Text(
 | 
			
		||||
                    title,
 | 
			
		||||
                    maxLines: 1,
 | 
			
		||||
                    overflow: TextOverflow.ellipsis,
 | 
			
		||||
                  ),
 | 
			
		||||
                  Text(
 | 
			
		||||
                    subtitle,
 | 
			
		||||
                    style: Theme.of(context)
 | 
			
		||||
                        .textTheme
 | 
			
		||||
                        .bodySmall!
 | 
			
		||||
                        .copyWith(color: Colors.white70),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Text(textRight)
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								frontend/lib/components/dashboard/header.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/lib/components/dashboard/header.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import 'package:aurcache/components/dashboard/search_field.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
import '../../utils/responsive.dart';
 | 
			
		||||
 | 
			
		||||
class Header extends StatelessWidget {
 | 
			
		||||
  const Header({
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: [
 | 
			
		||||
        if (!Responsive.isDesktop(context))
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: const Icon(Icons.menu),
 | 
			
		||||
            onPressed: () {},
 | 
			
		||||
          ),
 | 
			
		||||
        if (!Responsive.isMobile(context))
 | 
			
		||||
          Column(
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.start,
 | 
			
		||||
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
            children: [
 | 
			
		||||
              Text(
 | 
			
		||||
                "Hello, Arch User 👋",
 | 
			
		||||
                style: Theme.of(context).textTheme.headline6,
 | 
			
		||||
              ),
 | 
			
		||||
              const SizedBox(
 | 
			
		||||
                height: 8,
 | 
			
		||||
              ),
 | 
			
		||||
              Text(
 | 
			
		||||
                "Welcome to your Build server",
 | 
			
		||||
                style: Theme.of(context).textTheme.subtitle2,
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        if (!Responsive.isMobile(context))
 | 
			
		||||
          Spacer(flex: Responsive.isDesktop(context) ? 2 : 1),
 | 
			
		||||
        Expanded(child: SearchField()),
 | 
			
		||||
        ElevatedButton.icon(
 | 
			
		||||
          style: TextButton.styleFrom(
 | 
			
		||||
            backgroundColor: darkgreenColor,
 | 
			
		||||
            padding: EdgeInsets.symmetric(
 | 
			
		||||
              horizontal: defaultPadding * 1.5,
 | 
			
		||||
              vertical: defaultPadding / (Responsive.isMobile(context) ? 2 : 1),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          onPressed: () {},
 | 
			
		||||
          icon: const Icon(Icons.add),
 | 
			
		||||
          label: const Text(
 | 
			
		||||
            "Add New",
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        //ProfileCard()
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								frontend/lib/components/dashboard/quick_info_banner.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								frontend/lib/components/dashboard/quick_info_banner.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
import 'package:aurcache/components/dashboard/quick_info_tile.dart';
 | 
			
		||||
import 'package:aurcache/utils/file_formatter.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
import '../../models/quick_info_data.dart';
 | 
			
		||||
import '../../utils/responsive.dart';
 | 
			
		||||
import '../../models/stats.dart';
 | 
			
		||||
 | 
			
		||||
class QuickInfoBanner extends StatelessWidget {
 | 
			
		||||
  const QuickInfoBanner({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.stats,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final Stats stats;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final Size _size = MediaQuery.of(context).size;
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: [
 | 
			
		||||
        const SizedBox(height: defaultPadding),
 | 
			
		||||
        Responsive(
 | 
			
		||||
          mobile: _buildBanner(
 | 
			
		||||
            crossAxisCount: _size.width < 650 ? 2 : 4,
 | 
			
		||||
            childAspectRatio: _size.width < 650 ? 1.2 : 1,
 | 
			
		||||
          ),
 | 
			
		||||
          tablet: _buildBanner(),
 | 
			
		||||
          desktop: _buildBanner(
 | 
			
		||||
            childAspectRatio: _size.width < 1400 ? 2.75 : 2.75,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  List<QuickInfoData> buildQuickInfoData() {
 | 
			
		||||
    return [
 | 
			
		||||
      QuickInfoData(
 | 
			
		||||
          color: primaryColor,
 | 
			
		||||
          icon: Icons.widgets,
 | 
			
		||||
          title: "Total Packages",
 | 
			
		||||
          subtitle: stats.total_packages.toString()),
 | 
			
		||||
      QuickInfoData(
 | 
			
		||||
          color: const Color(0xFFFFA113),
 | 
			
		||||
          icon: Icons.hourglass_top,
 | 
			
		||||
          title: "Active Builds",
 | 
			
		||||
          subtitle: stats.active_builds.toString()),
 | 
			
		||||
      QuickInfoData(
 | 
			
		||||
          color: const Color(0xFFA4CDFF),
 | 
			
		||||
          icon: Icons.build,
 | 
			
		||||
          title: "Total Builds",
 | 
			
		||||
          subtitle: stats.total_builds.toString()),
 | 
			
		||||
      QuickInfoData(
 | 
			
		||||
          color: const Color(0xFFd50000),
 | 
			
		||||
          icon: Icons.storage,
 | 
			
		||||
          title: "Repo Size",
 | 
			
		||||
          subtitle: stats.repo_storage_size.readableFileSize()),
 | 
			
		||||
      QuickInfoData(
 | 
			
		||||
          color: const Color(0xFF00F260),
 | 
			
		||||
          icon: Icons.timelapse,
 | 
			
		||||
          title: "Average Build Time",
 | 
			
		||||
          subtitle: stats.avg_build_time.toString()),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildBanner({int crossAxisCount = 5, double childAspectRatio = 1}) {
 | 
			
		||||
    final quickInfo = buildQuickInfoData();
 | 
			
		||||
    return GridView.builder(
 | 
			
		||||
      physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
      shrinkWrap: true,
 | 
			
		||||
      itemCount: quickInfo.length,
 | 
			
		||||
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | 
			
		||||
        crossAxisCount: crossAxisCount,
 | 
			
		||||
        crossAxisSpacing: defaultPadding,
 | 
			
		||||
        mainAxisSpacing: defaultPadding,
 | 
			
		||||
        childAspectRatio: childAspectRatio,
 | 
			
		||||
      ),
 | 
			
		||||
      itemBuilder: (context, idx) => QuickInfoTile(data: quickInfo[idx]),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								frontend/lib/components/dashboard/quick_info_tile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								frontend/lib/components/dashboard/quick_info_tile.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
import 'package:aurcache/models/quick_info_data.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
 | 
			
		||||
class QuickInfoTile extends StatefulWidget {
 | 
			
		||||
  const QuickInfoTile({Key? key, required this.data}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final QuickInfoData data;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _QuickInfoTileState createState() => _QuickInfoTileState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _QuickInfoTileState extends State<QuickInfoTile> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: const EdgeInsets.all(defaultPadding),
 | 
			
		||||
      decoration: const BoxDecoration(
 | 
			
		||||
        color: secondaryColor,
 | 
			
		||||
        borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
      ),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
        children: [
 | 
			
		||||
          Container(
 | 
			
		||||
            padding: const EdgeInsets.all(defaultPadding * 0.75),
 | 
			
		||||
            height: 64,
 | 
			
		||||
            width: 64,
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              color: widget.data.color.withOpacity(0.1),
 | 
			
		||||
              borderRadius: const BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
            ),
 | 
			
		||||
            child: Icon(
 | 
			
		||||
              widget.data.icon,
 | 
			
		||||
              color: widget.data.color,
 | 
			
		||||
              size: 32,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Column(
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
            crossAxisAlignment: CrossAxisAlignment.end,
 | 
			
		||||
            children: [
 | 
			
		||||
              Text(
 | 
			
		||||
                widget.data.title,
 | 
			
		||||
                maxLines: 1,
 | 
			
		||||
                overflow: TextOverflow.ellipsis,
 | 
			
		||||
                style: Theme.of(context).textTheme.titleSmall,
 | 
			
		||||
              ),
 | 
			
		||||
              Text(
 | 
			
		||||
                widget.data.subtitle,
 | 
			
		||||
                style: Theme.of(context)
 | 
			
		||||
                    .textTheme
 | 
			
		||||
                    .titleLarge!
 | 
			
		||||
                    .copyWith(color: Colors.white70),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								frontend/lib/components/dashboard/recent_builds.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								frontend/lib/components/dashboard/recent_builds.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:aurcache/api/builds.dart';
 | 
			
		||||
import 'package:aurcache/models/build.dart';
 | 
			
		||||
import 'package:aurcache/components/dashboard/your_packages.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../api/API.dart';
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
 | 
			
		||||
class RecentBuilds extends StatefulWidget {
 | 
			
		||||
  const RecentBuilds({
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<RecentBuilds> createState() => _RecentBuildsState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _RecentBuildsState extends State<RecentBuilds> {
 | 
			
		||||
  late Future<List<Build>> dataFuture;
 | 
			
		||||
  Timer? timer;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    dataFuture = API.listAllBuilds();
 | 
			
		||||
 | 
			
		||||
    timer = Timer.periodic(
 | 
			
		||||
        const Duration(seconds: 10),
 | 
			
		||||
        (Timer t) => setState(() {
 | 
			
		||||
              dataFuture = API.listAllBuilds();
 | 
			
		||||
            }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
    timer?.cancel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: EdgeInsets.all(defaultPadding),
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
        color: secondaryColor,
 | 
			
		||||
        borderRadius: const BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
      ),
 | 
			
		||||
      child: Column(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
        children: [
 | 
			
		||||
          Text(
 | 
			
		||||
            "Recent Builds",
 | 
			
		||||
            style: Theme.of(context).textTheme.subtitle1,
 | 
			
		||||
          ),
 | 
			
		||||
          SizedBox(
 | 
			
		||||
            width: double.infinity,
 | 
			
		||||
            child: FutureBuilder(
 | 
			
		||||
                future: dataFuture,
 | 
			
		||||
                builder: (context, snapshot) {
 | 
			
		||||
                  if (snapshot.hasData) {
 | 
			
		||||
                    return DataTable(
 | 
			
		||||
                      horizontalMargin: 0,
 | 
			
		||||
                      columnSpacing: defaultPadding,
 | 
			
		||||
                      columns: const [
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Build ID"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Package Name"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Version"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Status"),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                      rows: snapshot.data!
 | 
			
		||||
                          .map((e) => recentUserDataRow(e))
 | 
			
		||||
                          .toList(),
 | 
			
		||||
                    );
 | 
			
		||||
                  } else {
 | 
			
		||||
                    return Text("no data");
 | 
			
		||||
                  }
 | 
			
		||||
                }),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  DataRow recentUserDataRow(Build build) {
 | 
			
		||||
    return DataRow(
 | 
			
		||||
      cells: [
 | 
			
		||||
        DataCell(Text(build.id.toString())),
 | 
			
		||||
        DataCell(Text(build.pkg_name)),
 | 
			
		||||
        DataCell(Text(build.version)),
 | 
			
		||||
        DataCell(Icon(
 | 
			
		||||
          switchSuccessIcon(build.status),
 | 
			
		||||
          color: switchSuccessColor(build.status),
 | 
			
		||||
        )),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								frontend/lib/components/dashboard/search_field.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								frontend/lib/components/dashboard/search_field.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
import 'package:aurcache/api/packages.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_svg/svg.dart';
 | 
			
		||||
 | 
			
		||||
import '../../api/API.dart';
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
 | 
			
		||||
class SearchField extends StatelessWidget {
 | 
			
		||||
  SearchField({
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final controller = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return TextField(
 | 
			
		||||
      controller: controller,
 | 
			
		||||
      decoration: InputDecoration(
 | 
			
		||||
        hintText: "Search",
 | 
			
		||||
        fillColor: secondaryColor,
 | 
			
		||||
        filled: true,
 | 
			
		||||
        border: const OutlineInputBorder(
 | 
			
		||||
          borderSide: BorderSide.none,
 | 
			
		||||
          borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
        ),
 | 
			
		||||
        suffixIcon: InkWell(
 | 
			
		||||
          onTap: () {
 | 
			
		||||
            // todo this is only temporary -> add this to a proper page
 | 
			
		||||
            API.addPackage(name: controller.text);
 | 
			
		||||
          },
 | 
			
		||||
          child: Container(
 | 
			
		||||
            padding: EdgeInsets.all(defaultPadding * 0.75),
 | 
			
		||||
            margin: EdgeInsets.symmetric(horizontal: defaultPadding / 2),
 | 
			
		||||
            decoration: const BoxDecoration(
 | 
			
		||||
              color: darkgreenColor,
 | 
			
		||||
              borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
            ),
 | 
			
		||||
            child: SvgPicture.asset(
 | 
			
		||||
              "assets/icons/Search.svg",
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								frontend/lib/components/dashboard/side_panel.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								frontend/lib/components/dashboard/side_panel.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import 'package:aurcache/components/dashboard/chart_card.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
import 'builds_chart.dart';
 | 
			
		||||
 | 
			
		||||
class SidePanel extends StatelessWidget {
 | 
			
		||||
  const SidePanel({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.nrbuilds,
 | 
			
		||||
    required this.nrfailedbuilds,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final int nrbuilds;
 | 
			
		||||
  final int nrfailedbuilds;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: EdgeInsets.all(defaultPadding),
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
        color: secondaryColor,
 | 
			
		||||
        borderRadius: const BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
      ),
 | 
			
		||||
      child: Column(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
        children: [
 | 
			
		||||
          const Text(
 | 
			
		||||
            "Package build success",
 | 
			
		||||
            style: TextStyle(
 | 
			
		||||
              fontSize: 18,
 | 
			
		||||
              fontWeight: FontWeight.w500,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          const SizedBox(height: defaultPadding),
 | 
			
		||||
          BuildsChart(nrbuilds: nrbuilds, nrfailedbuilds: nrfailedbuilds),
 | 
			
		||||
          ChartCard(
 | 
			
		||||
            color: const Color(0xff0a7005),
 | 
			
		||||
            title: "Successful Builds",
 | 
			
		||||
            textRight:
 | 
			
		||||
                "${((nrbuilds - nrfailedbuilds) * 100 / nrbuilds).toStringAsFixed(2)}%",
 | 
			
		||||
            subtitle: (nrbuilds - nrfailedbuilds).toString(),
 | 
			
		||||
          ),
 | 
			
		||||
          ChartCard(
 | 
			
		||||
            color: const Color(0xff760707),
 | 
			
		||||
            title: "Failed Builds",
 | 
			
		||||
            textRight:
 | 
			
		||||
                "${(nrfailedbuilds * 100 / nrbuilds).toStringAsFixed(2)}%",
 | 
			
		||||
            subtitle: nrfailedbuilds.toString(),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										162
									
								
								frontend/lib/components/dashboard/your_packages.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								frontend/lib/components/dashboard/your_packages.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:aurcache/api/packages.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../api/API.dart';
 | 
			
		||||
import '../../constants/color_constants.dart';
 | 
			
		||||
import '../../models/package.dart';
 | 
			
		||||
 | 
			
		||||
class YourPackages extends StatefulWidget {
 | 
			
		||||
  const YourPackages({
 | 
			
		||||
    Key? key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<YourPackages> createState() => _YourPackagesState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _YourPackagesState extends State<YourPackages> {
 | 
			
		||||
  late Future<List<Package>> dataFuture;
 | 
			
		||||
  Timer? timer;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    dataFuture = API.listPackages();
 | 
			
		||||
 | 
			
		||||
    timer = Timer.periodic(
 | 
			
		||||
        const Duration(seconds: 10),
 | 
			
		||||
        (Timer t) => setState(() {
 | 
			
		||||
              dataFuture = API.listPackages();
 | 
			
		||||
            }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
    timer?.cancel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: const EdgeInsets.all(defaultPadding),
 | 
			
		||||
      decoration: const BoxDecoration(
 | 
			
		||||
        color: secondaryColor,
 | 
			
		||||
        borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
      ),
 | 
			
		||||
      child: Column(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
        children: [
 | 
			
		||||
          Text(
 | 
			
		||||
            "Your Packages",
 | 
			
		||||
            style: Theme.of(context).textTheme.subtitle1,
 | 
			
		||||
          ),
 | 
			
		||||
          SingleChildScrollView(
 | 
			
		||||
            //scrollDirection: Axis.horizontal,
 | 
			
		||||
            child: SizedBox(
 | 
			
		||||
              width: double.infinity,
 | 
			
		||||
              child: FutureBuilder(
 | 
			
		||||
                builder: (context, snapshot) {
 | 
			
		||||
                  if (snapshot.hasData) {
 | 
			
		||||
                    return DataTable(
 | 
			
		||||
                      horizontalMargin: 0,
 | 
			
		||||
                      columnSpacing: defaultPadding,
 | 
			
		||||
                      columns: const [
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Package ID"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Package Name"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Number of versions"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Status"),
 | 
			
		||||
                        ),
 | 
			
		||||
                        DataColumn(
 | 
			
		||||
                          label: Text("Action"),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                      rows: snapshot.data!
 | 
			
		||||
                          .map((e) => buildDataRow(e))
 | 
			
		||||
                          .toList(growable: false),
 | 
			
		||||
                    );
 | 
			
		||||
                  } else {
 | 
			
		||||
                    return const Text("No data");
 | 
			
		||||
                  }
 | 
			
		||||
                },
 | 
			
		||||
                future: dataFuture,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  DataRow buildDataRow(Package package) {
 | 
			
		||||
    return DataRow(
 | 
			
		||||
      cells: [
 | 
			
		||||
        DataCell(Text(package.id.toString())),
 | 
			
		||||
        DataCell(Text(package.name)),
 | 
			
		||||
        DataCell(Text(package.count.toString())),
 | 
			
		||||
        DataCell(IconButton(
 | 
			
		||||
          icon: Icon(
 | 
			
		||||
            switchSuccessIcon(package.status),
 | 
			
		||||
            color: switchSuccessColor(package.status),
 | 
			
		||||
          ),
 | 
			
		||||
          onPressed: () {
 | 
			
		||||
            // todo open build info with logs
 | 
			
		||||
          },
 | 
			
		||||
        )),
 | 
			
		||||
        DataCell(
 | 
			
		||||
          Row(
 | 
			
		||||
            children: [
 | 
			
		||||
              TextButton(
 | 
			
		||||
                child: const Text('View', style: TextStyle(color: greenColor)),
 | 
			
		||||
                onPressed: () {},
 | 
			
		||||
              ),
 | 
			
		||||
              const SizedBox(
 | 
			
		||||
                width: 6,
 | 
			
		||||
              ),
 | 
			
		||||
              TextButton(
 | 
			
		||||
                child: const Text("Delete",
 | 
			
		||||
                    style: TextStyle(color: Colors.redAccent)),
 | 
			
		||||
                onPressed: () {},
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IconData switchSuccessIcon(int status) {
 | 
			
		||||
  switch (status) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return Icons.watch_later_outlined;
 | 
			
		||||
    case 1:
 | 
			
		||||
      return Icons.check_circle_outline;
 | 
			
		||||
    case 2:
 | 
			
		||||
      return Icons.cancel_outlined;
 | 
			
		||||
    default:
 | 
			
		||||
      return Icons.question_mark_outlined;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Color switchSuccessColor(int status) {
 | 
			
		||||
  switch (status) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return const Color(0xFF9D8D00);
 | 
			
		||||
    case 1:
 | 
			
		||||
      return const Color(0xFF0A6900);
 | 
			
		||||
    case 2:
 | 
			
		||||
      return const Color(0xff760707);
 | 
			
		||||
    default:
 | 
			
		||||
      return const Color(0xFF9D8D00);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user