From 600c2057fed000315e6892ea40488baf73894ee9 Mon Sep 17 00:00:00 2001 From: lukas-heiligenbrunner Date: Sat, 30 Dec 2023 00:45:33 +0100 Subject: [PATCH] add endpoint for general stats load build data to graph redesign top info tiles place add button on header folder restructure --- backend/src/api/backend.rs | 4 +- backend/src/api/list.rs | 66 ++++ backend/src/main.rs | 1 + backend/src/utils/dir_size.rs | 17 ++ backend/src/utils/mod.rs | 1 + frontend/lib/api/builds.dart | 4 +- frontend/lib/api/packages.dart | 6 +- frontend/lib/api/statistics.dart | 9 + .../app_button_widget.dart | 0 .../dashboard/builds_chart.dart} | 26 +- .../dashboard}/chart_card.dart | 23 +- frontend/lib/components/dashboard/header.dart | 59 ++++ .../dashboard/quick_info_banner.dart | 83 +++++ .../components/dashboard/quick_info_tile.dart | 65 ++++ .../dashboard}/recent_builds.dart | 13 +- .../components/dashboard/search_field.dart | 47 +++ .../dashboard/side_panel.dart} | 35 ++- .../dashboard}/your_packages.dart | 6 +- .../widgets => components}/input_widget.dart | 0 .../home => }/components/side_menu.dart | 2 +- .../{core/widgets => components}/wrapper.dart | 0 .../{core => }/constants/color_constants.dart | 2 +- frontend/lib/main.dart | 4 +- frontend/lib/{core => }/models/build.dart | 8 +- frontend/lib/models/daily_info_model.dart | 289 ------------------ frontend/lib/{core => }/models/package.dart | 0 frontend/lib/models/quick_info_data.dart | 15 + frontend/lib/models/slider_model.dart | 64 ---- frontend/lib/models/stats.dart | 30 ++ .../screens/dashboard/components/header.dart | 125 -------- .../components/mini_information_card.dart | 83 ----- .../components/mini_information_widget.dart | 206 ------------- .../screens/dashboard/dashboard_screen.dart | 57 ---- frontend/lib/screens/dashboard_screen.dart | 78 +++++ .../lib/screens/{home => }/home_screen.dart | 6 +- .../lib/{core => }/utils/colorful_tag.dart | 0 frontend/lib/utils/file_formatter.dart | 15 + frontend/lib/{ => utils}/responsive.dart | 0 38 files changed, 563 insertions(+), 886 deletions(-) create mode 100644 backend/src/utils/dir_size.rs create mode 100644 backend/src/utils/mod.rs create mode 100644 frontend/lib/api/statistics.dart rename frontend/lib/{core/widgets => components}/app_button_widget.dart (100%) rename frontend/lib/{screens/dashboard/components/charts.dart => components/dashboard/builds_chart.dart} (78%) rename frontend/lib/{screens/dashboard/components => components/dashboard}/chart_card.dart (73%) create mode 100644 frontend/lib/components/dashboard/header.dart create mode 100644 frontend/lib/components/dashboard/quick_info_banner.dart create mode 100644 frontend/lib/components/dashboard/quick_info_tile.dart rename frontend/lib/{screens/dashboard/components => components/dashboard}/recent_builds.dart (88%) create mode 100644 frontend/lib/components/dashboard/search_field.dart rename frontend/lib/{screens/dashboard/components/user_details_widget.dart => components/dashboard/side_panel.dart} (50%) rename frontend/lib/{screens/dashboard/components => components/dashboard}/your_packages.dart (96%) rename frontend/lib/{core/widgets => components}/input_widget.dart (100%) rename frontend/lib/{screens/home => }/components/side_menu.dart (97%) rename frontend/lib/{core/widgets => components}/wrapper.dart (100%) rename frontend/lib/{core => }/constants/color_constants.dart (92%) rename frontend/lib/{core => }/models/build.dart (74%) delete mode 100644 frontend/lib/models/daily_info_model.dart rename frontend/lib/{core => }/models/package.dart (100%) create mode 100644 frontend/lib/models/quick_info_data.dart delete mode 100644 frontend/lib/models/slider_model.dart create mode 100644 frontend/lib/models/stats.dart delete mode 100644 frontend/lib/screens/dashboard/components/header.dart delete mode 100644 frontend/lib/screens/dashboard/components/mini_information_card.dart delete mode 100644 frontend/lib/screens/dashboard/components/mini_information_widget.dart delete mode 100644 frontend/lib/screens/dashboard/dashboard_screen.dart create mode 100644 frontend/lib/screens/dashboard_screen.dart rename frontend/lib/screens/{home => }/home_screen.dart (87%) rename frontend/lib/{core => }/utils/colorful_tag.dart (100%) create mode 100644 frontend/lib/utils/file_formatter.dart rename frontend/lib/{ => utils}/responsive.dart (100%) diff --git a/backend/src/api/backend.rs b/backend/src/api/backend.rs index 1259e7c..b45185b 100644 --- a/backend/src/api/backend.rs +++ b/backend/src/api/backend.rs @@ -1,9 +1,10 @@ use crate::api::add::okapi_add_operation_for_package_add_; use crate::api::add::package_add; -use crate::api::list::okapi_add_operation_for_build_output_; use crate::api::list::okapi_add_operation_for_list_builds_; +use crate::api::list::okapi_add_operation_for_stats_; use crate::api::list::{build_output, okapi_add_operation_for_package_list_}; use crate::api::list::{list_builds, okapi_add_operation_for_search_}; +use crate::api::list::{okapi_add_operation_for_build_output_, stats}; use crate::api::list::{package_list, search}; use crate::api::remove::okapi_add_operation_for_package_del_; use crate::api::remove::okapi_add_operation_for_version_del_; @@ -20,5 +21,6 @@ pub fn build_api() -> Vec { version_del, build_output, list_builds, + stats ] } diff --git a/backend/src/api/list.rs b/backend/src/api/list.rs index 07c3d97..6fa40cc 100644 --- a/backend/src/api/list.rs +++ b/backend/src/api/list.rs @@ -2,14 +2,18 @@ use crate::aur::aur::query_aur; use crate::db::migration::JoinType; use crate::db::prelude::{Builds, Packages}; use crate::db::{builds, packages, versions}; +use crate::utils::dir_size::dir_size; use rocket::response::status::NotFound; use rocket::serde::json::Json; use rocket::serde::{Deserialize, Serialize}; use rocket::{get, State}; use rocket_okapi::okapi::schemars; use rocket_okapi::{openapi, JsonSchema}; +use sea_orm::PaginatorTrait; use sea_orm::{ColumnTrait, QueryFilter}; use sea_orm::{DatabaseConnection, EntityTrait, FromQueryResult, QuerySelect, RelationTrait}; +use std::path::PathBuf; +use std::{fs, io}; #[derive(Serialize, JsonSchema)] #[serde(crate = "rocket::serde")] @@ -149,3 +153,65 @@ pub async fn list_builds( Ok(Json(build)) } + +#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct ListStats { + total_builds: u32, + failed_builds: u32, + avg_queue_wait_time: u32, + avg_build_time: u32, + repo_storage_size: u32, + active_builds: u32, + total_packages: u32, +} + +#[openapi(tag = "test")] +#[get("/stats")] +pub async fn stats(db: &State) -> Result, NotFound> { + let db = db as &DatabaseConnection; + + return match get_stats(db).await { + Ok(v) => Ok(Json(v)), + Err(e) => Err(NotFound(e.to_string())), + }; +} + +async fn get_stats(db: &DatabaseConnection) -> anyhow::Result { + // Count total builds + let total_builds: u32 = Builds::find().count(db).await?.try_into()?; + + // Count failed builds + let failed_builds: u32 = Builds::find() + .filter(builds::Column::Status.eq(2)) + .count(db) + .await? + .try_into()?; + + // todo implement this values somehow + let avg_queue_wait_time: u32 = 42; + let avg_build_time: u32 = 42; + + // Calculate repo storage size + let repo_storage_size: u32 = dir_size("repo/").unwrap_or(0).try_into()?; + + // Count active builds + let active_builds: u32 = Builds::find() + .filter(builds::Column::Status.eq(0)) + .count(db) + .await? + .try_into()?; + + // Count total packages + let total_packages: u32 = Packages::find().count(db).await?.try_into()?; + + Ok(ListStats { + total_builds, + failed_builds, + avg_queue_wait_time, + avg_build_time, + repo_storage_size, + active_builds, + total_packages, + }) +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 181f5ed..ca61096 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -4,6 +4,7 @@ mod builder; mod db; mod pkgbuild; mod repo; +mod utils; use crate::api::backend; #[cfg(feature = "static")] diff --git a/backend/src/utils/dir_size.rs b/backend/src/utils/dir_size.rs new file mode 100644 index 0000000..e3c9df6 --- /dev/null +++ b/backend/src/utils/dir_size.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; +use std::{fs, io}; + +pub fn dir_size(path: impl Into) -> io::Result { + fn dir_size(mut dir: fs::ReadDir) -> io::Result { + dir.try_fold(0, |acc, file| { + let file = file?; + let size = match file.metadata()? { + data if data.is_dir() => dir_size(fs::read_dir(file.path())?)?, + data => data.len(), + }; + Ok(acc + size) + }) + } + + dir_size(fs::read_dir(path.into())?) +} diff --git a/backend/src/utils/mod.rs b/backend/src/utils/mod.rs new file mode 100644 index 0000000..a426004 --- /dev/null +++ b/backend/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod dir_size; diff --git a/frontend/lib/api/builds.dart b/frontend/lib/api/builds.dart index 0cd2c59..c7451e4 100644 --- a/frontend/lib/api/builds.dart +++ b/frontend/lib/api/builds.dart @@ -1,4 +1,4 @@ -import '../core/models/build.dart'; +import '../models/build.dart'; import 'api_client.dart'; extension BuildsAPI on ApiClient { @@ -7,7 +7,7 @@ extension BuildsAPI on ApiClient { final responseObject = resp.data as List; final List packages = - responseObject.map((e) => Build.fromJson(e)).toList(growable: false); + responseObject.map((e) => Build.fromJson(e)).toList(growable: false); return packages; } } diff --git a/frontend/lib/api/packages.dart b/frontend/lib/api/packages.dart index 8ab6287..f04fdbd 100644 --- a/frontend/lib/api/packages.dart +++ b/frontend/lib/api/packages.dart @@ -1,4 +1,4 @@ -import '../core/models/package.dart'; +import '../models/package.dart'; import 'api_client.dart'; extension PackagesAPI on ApiClient { @@ -15,8 +15,8 @@ extension PackagesAPI on ApiClient { } Future addPackage({bool force = false, required String name}) async { - final resp = await getRawClient().post("/packages/add", data: {'force_build': force, 'name': name}); + final resp = await getRawClient() + .post("/packages/add", data: {'force_build': force, 'name': name}); print(resp.data); } - } diff --git a/frontend/lib/api/statistics.dart b/frontend/lib/api/statistics.dart new file mode 100644 index 0000000..6dfdd46 --- /dev/null +++ b/frontend/lib/api/statistics.dart @@ -0,0 +1,9 @@ +import '../models/stats.dart'; +import 'api_client.dart'; + +extension StatsAPI on ApiClient { + Future listStats() async { + final resp = await getRawClient().get("/stats"); + return Stats.fromJson(resp.data); + } +} diff --git a/frontend/lib/core/widgets/app_button_widget.dart b/frontend/lib/components/app_button_widget.dart similarity index 100% rename from frontend/lib/core/widgets/app_button_widget.dart rename to frontend/lib/components/app_button_widget.dart diff --git a/frontend/lib/screens/dashboard/components/charts.dart b/frontend/lib/components/dashboard/builds_chart.dart similarity index 78% rename from frontend/lib/screens/dashboard/components/charts.dart rename to frontend/lib/components/dashboard/builds_chart.dart index afdc604..8f271d0 100644 --- a/frontend/lib/screens/dashboard/components/charts.dart +++ b/frontend/lib/components/dashboard/builds_chart.dart @@ -1,16 +1,21 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -class Chart extends StatefulWidget { - const Chart({ +class BuildsChart extends StatefulWidget { + const BuildsChart({ Key? key, + required this.nrbuilds, + required this.nrfailedbuilds, }) : super(key: key); + final int nrbuilds; + final int nrfailedbuilds; + @override - _ChartState createState() => _ChartState(); + _BuildsChartState createState() => _BuildsChartState(); } -class _ChartState extends State { +class _BuildsChartState extends State { int touchedIndex = -1; @override Widget build(BuildContext context) { @@ -31,9 +36,6 @@ class _ChartState extends State { pieTouchData: PieTouchData( touchCallback: (pieTouchResponse, touchresponse) { setState(() { - // final desiredTouch = pieTouchResponse.touchInput - // is! PointerExitEvent && - // pieTouchResponse.touchInput is! PointerUpEvent; if (touchresponse?.touchedSection != null) { touchedIndex = touchresponse! .touchedSection!.touchedSectionIndex; @@ -69,8 +71,9 @@ class _ChartState extends State { case 0: return PieChartSectionData( color: const Color(0xff760707), - value: 40, - title: '28.3%', + value: widget.nrfailedbuilds.toDouble(), + title: + "${(widget.nrfailedbuilds * 100 / widget.nrbuilds).toStringAsFixed(2)}%", radius: radius, titleStyle: TextStyle( fontSize: fontSize, @@ -80,8 +83,9 @@ class _ChartState extends State { case 1: return PieChartSectionData( color: const Color(0xff0a7005), - value: 30, - title: '16.7%', + value: (widget.nrbuilds - widget.nrfailedbuilds).toDouble(), + title: + "${((widget.nrbuilds - widget.nrfailedbuilds) * 100 / widget.nrbuilds).toStringAsFixed(2)}%", radius: radius, titleStyle: TextStyle( fontSize: fontSize, diff --git a/frontend/lib/screens/dashboard/components/chart_card.dart b/frontend/lib/components/dashboard/chart_card.dart similarity index 73% rename from frontend/lib/screens/dashboard/components/chart_card.dart rename to frontend/lib/components/dashboard/chart_card.dart index 221519d..72205dc 100644 --- a/frontend/lib/screens/dashboard/components/chart_card.dart +++ b/frontend/lib/components/dashboard/chart_card.dart @@ -1,25 +1,24 @@ import 'package:flutter/material.dart'; -import '../../../core/constants/color_constants.dart'; +import '../../constants/color_constants.dart'; -class UserDetailsMiniCard extends StatelessWidget { - const UserDetailsMiniCard({ +class ChartCard extends StatelessWidget { + const ChartCard({ Key? key, required this.title, required this.color, - required this.amountOfFiles, - required this.numberOfIncrease, + required this.textRight, + required this.subtitle, }) : super(key: key); final Color color; - final String title, amountOfFiles; - final int numberOfIncrease; + final String title, textRight, subtitle; @override Widget build(BuildContext context) { return Container( - margin: EdgeInsets.only(top: defaultPadding), - padding: EdgeInsets.all(defaultPadding), + 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( @@ -46,17 +45,17 @@ class UserDetailsMiniCard extends StatelessWidget { overflow: TextOverflow.ellipsis, ), Text( - "$numberOfIncrease", + subtitle, style: Theme.of(context) .textTheme - .caption! + .bodySmall! .copyWith(color: Colors.white70), ), ], ), ), ), - Text(amountOfFiles) + Text(textRight) ], ), ); diff --git a/frontend/lib/components/dashboard/header.dart b/frontend/lib/components/dashboard/header.dart new file mode 100644 index 0000000..0fb03c1 --- /dev/null +++ b/frontend/lib/components/dashboard/header.dart @@ -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() + ], + ); + } +} diff --git a/frontend/lib/components/dashboard/quick_info_banner.dart b/frontend/lib/components/dashboard/quick_info_banner.dart new file mode 100644 index 0000000..666f99f --- /dev/null +++ b/frontend/lib/components/dashboard/quick_info_banner.dart @@ -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 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]), + ); + } +} diff --git a/frontend/lib/components/dashboard/quick_info_tile.dart b/frontend/lib/components/dashboard/quick_info_tile.dart new file mode 100644 index 0000000..f5b66b5 --- /dev/null +++ b/frontend/lib/components/dashboard/quick_info_tile.dart @@ -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 { + @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), + ), + ], + ), + ], + ), + ); + } +} diff --git a/frontend/lib/screens/dashboard/components/recent_builds.dart b/frontend/lib/components/dashboard/recent_builds.dart similarity index 88% rename from frontend/lib/screens/dashboard/components/recent_builds.dart rename to frontend/lib/components/dashboard/recent_builds.dart index 39947a0..e23a9c7 100644 --- a/frontend/lib/screens/dashboard/components/recent_builds.dart +++ b/frontend/lib/components/dashboard/recent_builds.dart @@ -1,13 +1,12 @@ import 'dart:async'; import 'package:aurcache/api/builds.dart'; -import 'package:aurcache/core/models/build.dart'; -import 'package:aurcache/screens/dashboard/components/your_packages.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 '../../../core/constants/color_constants.dart'; -import '../../../core/models/package.dart'; +import '../../api/API.dart'; +import '../../constants/color_constants.dart'; class RecentBuilds extends StatefulWidget { const RecentBuilds({ @@ -78,7 +77,9 @@ class _RecentBuildsState extends State { label: Text("Status"), ), ], - rows: snapshot.data!.map((e) => recentUserDataRow(e)).toList(), + rows: snapshot.data! + .map((e) => recentUserDataRow(e)) + .toList(), ); } else { return Text("no data"); diff --git a/frontend/lib/components/dashboard/search_field.dart b/frontend/lib/components/dashboard/search_field.dart new file mode 100644 index 0000000..8175f30 --- /dev/null +++ b/frontend/lib/components/dashboard/search_field.dart @@ -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", + ), + ), + ), + ), + ); + } +} diff --git a/frontend/lib/screens/dashboard/components/user_details_widget.dart b/frontend/lib/components/dashboard/side_panel.dart similarity index 50% rename from frontend/lib/screens/dashboard/components/user_details_widget.dart rename to frontend/lib/components/dashboard/side_panel.dart index efdb876..37a21f8 100644 --- a/frontend/lib/screens/dashboard/components/user_details_widget.dart +++ b/frontend/lib/components/dashboard/side_panel.dart @@ -1,14 +1,19 @@ -import 'package:aurcache/screens/dashboard/components/chart_card.dart'; +import 'package:aurcache/components/dashboard/chart_card.dart'; import 'package:flutter/material.dart'; -import '../../../core/constants/color_constants.dart'; -import 'charts.dart'; +import '../../constants/color_constants.dart'; +import 'builds_chart.dart'; -class UserDetailsWidget extends StatelessWidget { - const UserDetailsWidget({ +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( @@ -20,26 +25,28 @@ class UserDetailsWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( "Package build success", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), ), - SizedBox(height: defaultPadding), - Chart(), - UserDetailsMiniCard( + const SizedBox(height: defaultPadding), + BuildsChart(nrbuilds: nrbuilds, nrfailedbuilds: nrfailedbuilds), + ChartCard( color: const Color(0xff0a7005), title: "Successful Builds", - amountOfFiles: "%16.7", - numberOfIncrease: 1328, + textRight: + "${((nrbuilds - nrfailedbuilds) * 100 / nrbuilds).toStringAsFixed(2)}%", + subtitle: (nrbuilds - nrfailedbuilds).toString(), ), - UserDetailsMiniCard( + ChartCard( color: const Color(0xff760707), title: "Failed Builds", - amountOfFiles: "%28.3", - numberOfIncrease: 1328, + textRight: + "${(nrfailedbuilds * 100 / nrbuilds).toStringAsFixed(2)}%", + subtitle: nrfailedbuilds.toString(), ), ], ), diff --git a/frontend/lib/screens/dashboard/components/your_packages.dart b/frontend/lib/components/dashboard/your_packages.dart similarity index 96% rename from frontend/lib/screens/dashboard/components/your_packages.dart rename to frontend/lib/components/dashboard/your_packages.dart index 82c7f08..c2c4357 100644 --- a/frontend/lib/screens/dashboard/components/your_packages.dart +++ b/frontend/lib/components/dashboard/your_packages.dart @@ -3,9 +3,9 @@ import 'dart:async'; import 'package:aurcache/api/packages.dart'; import 'package:flutter/material.dart'; -import '../../../api/API.dart'; -import '../../../core/constants/color_constants.dart'; -import '../../../core/models/package.dart'; +import '../../api/API.dart'; +import '../../constants/color_constants.dart'; +import '../../models/package.dart'; class YourPackages extends StatefulWidget { const YourPackages({ diff --git a/frontend/lib/core/widgets/input_widget.dart b/frontend/lib/components/input_widget.dart similarity index 100% rename from frontend/lib/core/widgets/input_widget.dart rename to frontend/lib/components/input_widget.dart diff --git a/frontend/lib/screens/home/components/side_menu.dart b/frontend/lib/components/side_menu.dart similarity index 97% rename from frontend/lib/screens/home/components/side_menu.dart rename to frontend/lib/components/side_menu.dart index ef1f3a0..911df9c 100644 --- a/frontend/lib/screens/home/components/side_menu.dart +++ b/frontend/lib/components/side_menu.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import '../../../core/constants/color_constants.dart'; +import '../constants/color_constants.dart'; class SideMenu extends StatelessWidget { const SideMenu({ diff --git a/frontend/lib/core/widgets/wrapper.dart b/frontend/lib/components/wrapper.dart similarity index 100% rename from frontend/lib/core/widgets/wrapper.dart rename to frontend/lib/components/wrapper.dart diff --git a/frontend/lib/core/constants/color_constants.dart b/frontend/lib/constants/color_constants.dart similarity index 92% rename from frontend/lib/core/constants/color_constants.dart rename to frontend/lib/constants/color_constants.dart index 9c30d16..28b7ce7 100644 --- a/frontend/lib/core/constants/color_constants.dart +++ b/frontend/lib/constants/color_constants.dart @@ -6,7 +6,7 @@ const primaryColor = Color(0xFF2697FF); const secondaryColor = Color(0xFF292929); const bgColor = Color(0xFF212121); -const darkgreenColor = Color(0xFF2c614f); +const darkgreenColor = Color(0xff0a7005); const greenColor = Color(0xFF6bab58); const defaultPadding = 16.0; diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index cc63ae7..23b0b74 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,8 +1,8 @@ -import 'package:aurcache/screens/home/home_screen.dart'; +import 'package:aurcache/screens/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'core/constants/color_constants.dart'; +import 'constants/color_constants.dart'; void main() { runApp(const MyApp()); diff --git a/frontend/lib/core/models/build.dart b/frontend/lib/models/build.dart similarity index 74% rename from frontend/lib/core/models/build.dart rename to frontend/lib/models/build.dart index 96f90c5..6b472ce 100644 --- a/frontend/lib/core/models/build.dart +++ b/frontend/lib/models/build.dart @@ -5,8 +5,10 @@ class Build { final int status; Build( - {required this.id,required this.pkg_name, required this.version, - required this.status}); + {required this.id, + required this.pkg_name, + required this.version, + required this.status}); factory Build.fromJson(Map json) { return Build( @@ -16,4 +18,4 @@ class Build { version: json["version"] as String, ); } -} \ No newline at end of file +} diff --git a/frontend/lib/models/daily_info_model.dart b/frontend/lib/models/daily_info_model.dart deleted file mode 100644 index f15adb6..0000000 --- a/frontend/lib/models/daily_info_model.dart +++ /dev/null @@ -1,289 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; - -import '../core/constants/color_constants.dart'; - -class DailyInfoModel { - IconData? icon; - String? title; - String? totalStorage; - int? volumeData; - int? percentage; - Color? color; - List? colors; - List? spots; - - DailyInfoModel({ - this.icon, - this.title, - this.totalStorage, - this.volumeData, - this.percentage, - this.color, - this.colors, - this.spots, - }); - - DailyInfoModel.fromJson(Map json) { - title = json['title']; - volumeData = json['volumeData']; - icon = json['icon']; - totalStorage = json['totalStorage']; - color = json['color']; - percentage = json['percentage']; - colors = json['colors']; - spots = json['spots']; - } - - Map toJson() { - final Map data = new Map(); - data['title'] = this.title; - data['volumeData'] = this.volumeData; - data['icon'] = this.icon; - data['totalStorage'] = this.totalStorage; - data['color'] = this.color; - data['percentage'] = this.percentage; - data['colors'] = this.colors; - data['spots'] = this.spots; - return data; - } -} - -List dailyDatas = - dailyData.map((item) => DailyInfoModel.fromJson(item)).toList(); - -//List spots = yValues.asMap().entries.map((e) { -// return FlSpot(e.key.toDouble(), e.value); -//}).toList(); - -var dailyData = [ - { - "title": "Employee", - "volumeData": 1328, - "icon": Icons.ac_unit, - "totalStorage": "+ %20", - "color": primaryColor, - "percentage": 35, - "colors": [ - Color(0xff23b6e6), - Color(0xff02d39a), - ], - "spots": [ - FlSpot( - 1, - 2, - ), - FlSpot( - 2, - 1.0, - ), - FlSpot( - 3, - 1.8, - ), - FlSpot( - 4, - 1.5, - ), - FlSpot( - 5, - 1.0, - ), - FlSpot( - 6, - 2.2, - ), - FlSpot( - 7, - 1.8, - ), - FlSpot( - 8, - 1.5, - ) - ] - }, - { - "title": "On Leave", - "volumeData": 1328, - "icon": Icons.ac_unit, - "totalStorage": "+ %5", - "color": Color(0xFFFFA113), - "percentage": 35, - "colors": [Color(0xfff12711), Color(0xfff5af19)], - "spots": [ - FlSpot( - 1, - 1.3, - ), - FlSpot( - 2, - 1.0, - ), - FlSpot( - 3, - 4, - ), - FlSpot( - 4, - 1.5, - ), - FlSpot( - 5, - 1.0, - ), - FlSpot( - 6, - 3, - ), - FlSpot( - 7, - 1.8, - ), - FlSpot( - 8, - 1.5, - ) - ] - }, - { - "title": "Onboarding", - "volumeData": 1328, - "icon": Icons.ac_unit, - "totalStorage": "+ %8", - "color": Color(0xFFA4CDFF), - "percentage": 10, - "colors": [Color(0xff2980B9), Color(0xff6DD5FA)], - "spots": [ - FlSpot( - 1, - 1.3, - ), - FlSpot( - 2, - 5, - ), - FlSpot( - 3, - 1.8, - ), - FlSpot( - 4, - 6, - ), - FlSpot( - 5, - 1.0, - ), - FlSpot( - 6, - 2.2, - ), - FlSpot( - 7, - 1.8, - ), - FlSpot( - 8, - 1, - ) - ] - }, - { - "title": "Open Position", - "volumeData": 1328, - "icon": Icons.ac_unit, - "totalStorage": "+ %8", - "color": Color(0xFFd50000), - "percentage": 10, - "colors": [Color(0xff93291E), Color(0xffED213A)], - "spots": [ - FlSpot( - 1, - 3, - ), - FlSpot( - 2, - 4, - ), - FlSpot( - 3, - 1.8, - ), - FlSpot( - 4, - 1.5, - ), - FlSpot( - 5, - 1.0, - ), - FlSpot( - 6, - 2.2, - ), - FlSpot( - 7, - 1.8, - ), - FlSpot( - 8, - 1.5, - ) - ] - }, - { - "title": "Efficiency", - "volumeData": 5328, - "icon": Icons.ac_unit, - "totalStorage": "- %5", - "color": Color(0xFF00F260), - "percentage": 78, - "colors": [Color(0xff0575E6), Color(0xff00F260)], - "spots": [ - FlSpot( - 1, - 1.3, - ), - FlSpot( - 2, - 1.0, - ), - FlSpot( - 3, - 1.8, - ), - FlSpot( - 4, - 1.5, - ), - FlSpot( - 5, - 1.0, - ), - FlSpot( - 6, - 2.2, - ), - FlSpot( - 7, - 1.8, - ), - FlSpot( - 8, - 1.5, - ) - ] - } -]; - -//final List yValues = [ -// 2.3, -// 1.8, -// 1.9, -// 1.5, -// 1.0, -// 2.2, -// 1.8, -// 1.5, -//]; diff --git a/frontend/lib/core/models/package.dart b/frontend/lib/models/package.dart similarity index 100% rename from frontend/lib/core/models/package.dart rename to frontend/lib/models/package.dart diff --git a/frontend/lib/models/quick_info_data.dart b/frontend/lib/models/quick_info_data.dart new file mode 100644 index 0000000..25c80e4 --- /dev/null +++ b/frontend/lib/models/quick_info_data.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class QuickInfoData { + const QuickInfoData({ + Key? key, + required this.color, + required this.icon, + required this.title, + required this.subtitle, + }); + + final Color color; + final IconData icon; + final String title, subtitle; +} diff --git a/frontend/lib/models/slider_model.dart b/frontend/lib/models/slider_model.dart deleted file mode 100644 index 505439e..0000000 --- a/frontend/lib/models/slider_model.dart +++ /dev/null @@ -1,64 +0,0 @@ -class SliderModel { - String? image; - String? text; - String? altText; - String? bAltText; - String? productImage; - int? kBackgroundColor; - - SliderModel(this.image, this.text, this.altText, this.bAltText, - this.productImage, this.kBackgroundColor); - - SliderModel.fromJson(Map json) { - image = json['image']; - kBackgroundColor = json['kBackgroundColor']; - text = json['text']; - altText = json['altText']; - bAltText = json['bAltText']; - productImage = json['productImage']; - } - - Map toJson() { - final Map data = new Map(); - data['image'] = this.image; - data['kBackgroundColor'] = this.kBackgroundColor; - data['text'] = this.text; - data['altText'] = this.altText; - data['bAltText'] = this.bAltText; - data['productImage'] = this.productImage; - return data; - } -} - -List slides = - slideData.map((item) => SliderModel.fromJson(item)).toList(); - -var slideData = [ - { - "image": "assets/slides/background-1.jpeg", - "kBackgroundColor": 0xFF2c614f, - "text": "Welcome to the Smart Smart Admin Dashboard!", - "altText": "You can access & track your services in real-time.", - "bAltText": "Are you ready for the next generation AI supported Dashboard?", - "productImage": "assets/images/mockup.png" - }, - { - "image": "assets/slides/background-2.jpeg", - "kBackgroundColor": 0xFF8a1a4c, - "text": "¡Bienvenido al tablero Smart Admin Dashboard!", - "altText": "Puede acceder y rastrear sus servicios en tiempo real.", - "bAltText": - "¿Estás listo para el panel de control impulsado por IA de próxima generación?", - "productImage": "assets/images/mockup-2.png" - }, - { - "image": "assets/slides/background-3.jpeg", - "kBackgroundColor": 0xFF0ab3ec, - "text": "Willkommen im Smart Admin Dashboard!", - "altText": - "Sie können in Echtzeit auf Ihre Dienste zugreifen und diese verfolgen.", - "bAltText": - "Sind Sie bereit für das AI-unterstützte Dashboard der nächsten Generation?", - "productImage": "assets/images/mockup-3.png" - } -]; diff --git a/frontend/lib/models/stats.dart b/frontend/lib/models/stats.dart new file mode 100644 index 0000000..9698d22 --- /dev/null +++ b/frontend/lib/models/stats.dart @@ -0,0 +1,30 @@ +class Stats { + final int total_builds, + failed_builds, + avg_queue_wait_time, + avg_build_time, + repo_storage_size, + active_builds, + total_packages; + + factory Stats.fromJson(Map json) { + return Stats( + total_builds: json["total_builds"] as int, + failed_builds: json["failed_builds"] as int, + avg_queue_wait_time: json["avg_queue_wait_time"] as int, + avg_build_time: json["avg_build_time"] as int, + repo_storage_size: json["repo_storage_size"] as int, + active_builds: json["active_builds"] as int, + total_packages: json["total_packages"] as int, + ); + } + + Stats( + {required this.total_builds, + required this.failed_builds, + required this.avg_queue_wait_time, + required this.avg_build_time, + required this.repo_storage_size, + required this.active_builds, + required this.total_packages}); +} diff --git a/frontend/lib/screens/dashboard/components/header.dart b/frontend/lib/screens/dashboard/components/header.dart deleted file mode 100644 index 97afde5..0000000 --- a/frontend/lib/screens/dashboard/components/header.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:aurcache/api/packages.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -import '../../../api/API.dart'; -import '../../../core/constants/color_constants.dart'; -import '../../../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: 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, - ), - 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()), - //ProfileCard() - ], - ); - } -} - -class ProfileCard extends StatelessWidget { - const ProfileCard({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.only(left: defaultPadding), - padding: EdgeInsets.symmetric( - horizontal: defaultPadding, - vertical: defaultPadding / 2, - ), - decoration: BoxDecoration( - color: secondaryColor, - borderRadius: const BorderRadius.all(Radius.circular(10)), - border: Border.all(color: Colors.white10), - ), - child: Row( - children: [ - CircleAvatar( - backgroundImage: AssetImage("assets/images/profile_pic.png"), - ), - if (!Responsive.isMobile(context)) - Padding( - padding: - const EdgeInsets.symmetric(horizontal: defaultPadding / 2), - child: Text("Deniz Çolak"), - ), - Icon(Icons.keyboard_arrow_down), - ], - ), - ); - } -} - -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: greenColor, - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - child: SvgPicture.asset( - "assets/icons/Search.svg", - ), - ), - ), - ), - ); - } -} diff --git a/frontend/lib/screens/dashboard/components/mini_information_card.dart b/frontend/lib/screens/dashboard/components/mini_information_card.dart deleted file mode 100644 index 77ff36e..0000000 --- a/frontend/lib/screens/dashboard/components/mini_information_card.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../core/constants/color_constants.dart'; -import '../../../models/daily_info_model.dart'; -import '../../../responsive.dart'; -import 'mini_information_widget.dart'; - -class MiniInformation extends StatelessWidget { - const MiniInformation({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final Size _size = MediaQuery.of(context).size; - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: 10, - ), - ElevatedButton.icon( - style: TextButton.styleFrom( - backgroundColor: Colors.green, - padding: EdgeInsets.symmetric( - horizontal: defaultPadding * 1.5, - vertical: - defaultPadding / (Responsive.isMobile(context) ? 2 : 1), - ), - ), - onPressed: () {}, - icon: Icon(Icons.add), - label: Text( - "Add New", - ), - ), - ], - ), - SizedBox(height: defaultPadding), - Responsive( - mobile: InformationCard( - crossAxisCount: _size.width < 650 ? 2 : 4, - childAspectRatio: _size.width < 650 ? 1.2 : 1, - ), - tablet: InformationCard(), - desktop: InformationCard( - childAspectRatio: _size.width < 1400 ? 1.2 : 1.4, - ), - ), - ], - ); - } -} - -class InformationCard extends StatelessWidget { - const InformationCard({ - Key? key, - this.crossAxisCount = 5, - this.childAspectRatio = 1, - }) : super(key: key); - - final int crossAxisCount; - final double childAspectRatio; - - @override - Widget build(BuildContext context) { - return GridView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: dailyDatas.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - crossAxisSpacing: defaultPadding, - mainAxisSpacing: defaultPadding, - childAspectRatio: childAspectRatio, - ), - itemBuilder: (context, index) => - MiniInformationWidget(dailyData: dailyDatas[index]), - ); - } -} diff --git a/frontend/lib/screens/dashboard/components/mini_information_widget.dart b/frontend/lib/screens/dashboard/components/mini_information_widget.dart deleted file mode 100644 index 772e247..0000000 --- a/frontend/lib/screens/dashboard/components/mini_information_widget.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; - -import '../../../core/constants/color_constants.dart'; -import '../../../models/daily_info_model.dart'; - -class MiniInformationWidget extends StatefulWidget { - const MiniInformationWidget({ - Key? key, - required this.dailyData, - }) : super(key: key); - final DailyInfoModel dailyData; - - @override - _MiniInformationWidgetState createState() => _MiniInformationWidgetState(); -} - -int _value = 1; - -class _MiniInformationWidgetState extends State { - @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, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: EdgeInsets.all(defaultPadding * 0.75), - height: 40, - width: 40, - decoration: BoxDecoration( - color: widget.dailyData.color!.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - child: Icon( - widget.dailyData.icon, - color: widget.dailyData.color, - size: 18, - ), - ), - Padding( - padding: EdgeInsets.only(right: 12.0), - child: DropdownButton( - icon: Icon(Icons.more_vert, size: 18), - underline: SizedBox(), - style: Theme.of(context).textTheme.button, - value: _value, - items: [ - DropdownMenuItem( - child: Text("Daily"), - value: 1, - ), - DropdownMenuItem( - child: Text("Weekly"), - value: 2, - ), - DropdownMenuItem( - child: Text("Monthly"), - value: 3, - ), - ], - onChanged: (int? value) { - setState(() { - _value = value!; - }); - }, - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - widget.dailyData.title!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - SizedBox( - height: 8, - ), - Container( - child: LineChartWidget( - colors: widget.dailyData.colors, - spotsData: widget.dailyData.spots, - ), - ) - ], - ), - SizedBox( - height: 8, - ), - ProgressLine( - color: widget.dailyData.color!, - percentage: widget.dailyData.percentage!, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${widget.dailyData.volumeData}", - style: Theme.of(context) - .textTheme - .caption! - .copyWith(color: Colors.white70), - ), - Text( - widget.dailyData.totalStorage!, - style: Theme.of(context) - .textTheme - .caption! - .copyWith(color: Colors.white), - ), - ], - ) - ], - ), - ); - } -} - -class LineChartWidget extends StatelessWidget { - const LineChartWidget({ - Key? key, - required this.colors, - required this.spotsData, - }) : super(key: key); - final List? colors; - final List? spotsData; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Container( - width: 80, - height: 30, - child: LineChart( - LineChartData( - lineBarsData: [ - LineChartBarData( - spots: spotsData!, - belowBarData: BarAreaData(show: false), - aboveBarData: BarAreaData(show: false), - isCurved: true, - dotData: FlDotData(show: false), - //colors: colors, - barWidth: 3), - ], - lineTouchData: LineTouchData(enabled: false), - titlesData: FlTitlesData(show: false), - //axisTitleData: FlAxisTitleData(show: false), - gridData: FlGridData(show: false), - borderData: FlBorderData(show: false)), - ), - ), - ], - ); - } -} - -class ProgressLine extends StatelessWidget { - const ProgressLine({ - Key? key, - this.color = primaryColor, - required this.percentage, - }) : super(key: key); - - final Color color; - final int percentage; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Container( - width: double.infinity, - height: 5, - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - ), - LayoutBuilder( - builder: (context, constraints) => Container( - width: constraints.maxWidth * (percentage / 100), - height: 5, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - ), - ), - ], - ); - } -} diff --git a/frontend/lib/screens/dashboard/dashboard_screen.dart b/frontend/lib/screens/dashboard/dashboard_screen.dart deleted file mode 100644 index 8b03e8b..0000000 --- a/frontend/lib/screens/dashboard/dashboard_screen.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../core/constants/color_constants.dart'; -import '../../responsive.dart'; -import 'components/header.dart'; -import 'components/mini_information_card.dart'; -import 'components/recent_builds.dart'; -import 'components/your_packages.dart'; -import 'components/user_details_widget.dart'; - -class DashboardScreen extends StatelessWidget { - @override - Widget build(BuildContext context) { - return SafeArea( - child: SingleChildScrollView( - //padding: EdgeInsets.all(defaultPadding), - child: Container( - padding: EdgeInsets.all(defaultPadding), - child: Column( - children: [ - Header(), - SizedBox(height: defaultPadding), - MiniInformation(), - SizedBox(height: defaultPadding), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 5, - child: Column( - children: [ - YourPackages(), - SizedBox(height: defaultPadding), - RecentBuilds(), - if (Responsive.isMobile(context)) - SizedBox(height: defaultPadding), - if (Responsive.isMobile(context)) UserDetailsWidget(), - ], - ), - ), - if (!Responsive.isMobile(context)) - SizedBox(width: defaultPadding), - // On Mobile means if the screen is less than 850 we dont want to show it - if (!Responsive.isMobile(context)) - Expanded( - flex: 2, - child: UserDetailsWidget(), - ), - ], - ) - ], - ), - ), - ), - ); - } -} diff --git a/frontend/lib/screens/dashboard_screen.dart b/frontend/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..a5520c2 --- /dev/null +++ b/frontend/lib/screens/dashboard_screen.dart @@ -0,0 +1,78 @@ +import 'package:aurcache/api/statistics.dart'; +import 'package:flutter/material.dart'; + +import '../api/API.dart'; +import '../components/dashboard/header.dart'; +import '../constants/color_constants.dart'; +import '../utils/responsive.dart'; +import '../models/stats.dart'; +import '../components/dashboard/quick_info_banner.dart'; +import '../components/dashboard/recent_builds.dart'; +import '../components/dashboard/your_packages.dart'; +import '../components/dashboard/side_panel.dart'; + +class DashboardScreen extends StatelessWidget { + final stats = API.listStats(); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: stats, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + final Stats stats = snapshot.data!; + + return SafeArea( + child: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(defaultPadding), + child: Column( + children: [ + const Header(), + const SizedBox(height: defaultPadding), + QuickInfoBanner( + stats: stats, + ), + const SizedBox(height: defaultPadding), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 5, + child: Column( + children: [ + const YourPackages(), + const SizedBox(height: defaultPadding), + const RecentBuilds(), + if (Responsive.isMobile(context)) + const SizedBox(height: defaultPadding), + if (Responsive.isMobile(context)) + SidePanel( + nrbuilds: stats.total_builds, + nrfailedbuilds: stats.failed_builds), + ], + ), + ), + if (!Responsive.isMobile(context)) + const SizedBox(width: defaultPadding), + // On Mobile means if the screen is less than 850 we dont want to show it + if (!Responsive.isMobile(context)) + Expanded( + flex: 2, + child: SidePanel( + nrbuilds: stats.total_builds, + nrfailedbuilds: stats.failed_builds), + ), + ], + ) + ], + ), + ), + ), + ); + } else { + return const Text("loading"); + } + }); + } +} diff --git a/frontend/lib/screens/home/home_screen.dart b/frontend/lib/screens/home_screen.dart similarity index 87% rename from frontend/lib/screens/home/home_screen.dart rename to frontend/lib/screens/home_screen.dart index 895b58f..b0db80e 100644 --- a/frontend/lib/screens/home/home_screen.dart +++ b/frontend/lib/screens/home_screen.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import '../../responsive.dart'; -import '../dashboard/dashboard_screen.dart'; -import 'components/side_menu.dart'; +import '../utils/responsive.dart'; +import 'dashboard_screen.dart'; +import '../components/side_menu.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); diff --git a/frontend/lib/core/utils/colorful_tag.dart b/frontend/lib/utils/colorful_tag.dart similarity index 100% rename from frontend/lib/core/utils/colorful_tag.dart rename to frontend/lib/utils/colorful_tag.dart diff --git a/frontend/lib/utils/file_formatter.dart b/frontend/lib/utils/file_formatter.dart new file mode 100644 index 0000000..59f25f9 --- /dev/null +++ b/frontend/lib/utils/file_formatter.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +double _log10(num x) => log(x) / ln10; + +extension FileFormatter on num { + String readableFileSize({bool base1024 = true}) { + if (this <= 0) return '0'; + final base = base1024 ? 1024 : 1000; + final units = base1024 + ? ['Bi', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'] + : ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; + final digitGroups = (_log10(this) / _log10(base)).floor(); + return '${(this / pow(base, digitGroups)).toStringAsFixed(2)} ${units[digitGroups]}'; + } +} diff --git a/frontend/lib/responsive.dart b/frontend/lib/utils/responsive.dart similarity index 100% rename from frontend/lib/responsive.dart rename to frontend/lib/utils/responsive.dart