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:
parent
ce7a260760
commit
600c2057fe
@ -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<Route> {
|
||||
version_del,
|
||||
build_output,
|
||||
list_builds,
|
||||
stats
|
||||
]
|
||||
}
|
||||
|
@ -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<DatabaseConnection>) -> Result<Json<ListStats>, NotFound<String>> {
|
||||
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<ListStats> {
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ mod builder;
|
||||
mod db;
|
||||
mod pkgbuild;
|
||||
mod repo;
|
||||
mod utils;
|
||||
|
||||
use crate::api::backend;
|
||||
#[cfg(feature = "static")]
|
||||
|
17
backend/src/utils/dir_size.rs
Normal file
17
backend/src/utils/dir_size.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
|
||||
pub fn dir_size(path: impl Into<PathBuf>) -> io::Result<u64> {
|
||||
fn dir_size(mut dir: fs::ReadDir) -> io::Result<u64> {
|
||||
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())?)
|
||||
}
|
1
backend/src/utils/mod.rs
Normal file
1
backend/src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod dir_size;
|
@ -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<Build> packages =
|
||||
responseObject.map((e) => Build.fromJson(e)).toList(growable: false);
|
||||
responseObject.map((e) => Build.fromJson(e)).toList(growable: false);
|
||||
return packages;
|
||||
}
|
||||
}
|
||||
|
@ -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<void> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
9
frontend/lib/api/statistics.dart
Normal file
9
frontend/lib/api/statistics.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import '../models/stats.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
extension StatsAPI on ApiClient {
|
||||
Future<Stats> listStats() async {
|
||||
final resp = await getRawClient().get("/stats");
|
||||
return Stats.fromJson(resp.data);
|
||||
}
|
||||
}
|
@ -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<Chart> {
|
||||
class _BuildsChartState extends State<BuildsChart> {
|
||||
int touchedIndex = -1;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -31,9 +36,6 @@ class _ChartState extends State<Chart> {
|
||||
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<Chart> {
|
||||
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<Chart> {
|
||||
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,
|
@ -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)
|
||||
],
|
||||
),
|
||||
);
|
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<RecentBuilds> {
|
||||
label: Text("Status"),
|
||||
),
|
||||
],
|
||||
rows: snapshot.data!.map((e) => recentUserDataRow(e)).toList(),
|
||||
rows: snapshot.data!
|
||||
.map((e) => recentUserDataRow(e))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
return Text("no data");
|
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",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
@ -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({
|
@ -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({
|
@ -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;
|
@ -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());
|
||||
|
@ -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<String, dynamic> json) {
|
||||
return Build(
|
||||
@ -16,4 +18,4 @@ class Build {
|
||||
version: json["version"] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Color>? colors;
|
||||
List<FlSpot>? spots;
|
||||
|
||||
DailyInfoModel({
|
||||
this.icon,
|
||||
this.title,
|
||||
this.totalStorage,
|
||||
this.volumeData,
|
||||
this.percentage,
|
||||
this.color,
|
||||
this.colors,
|
||||
this.spots,
|
||||
});
|
||||
|
||||
DailyInfoModel.fromJson(Map<String, dynamic> 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
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<DailyInfoModel> dailyDatas =
|
||||
dailyData.map((item) => DailyInfoModel.fromJson(item)).toList();
|
||||
|
||||
//List<FlSpot> 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<double> yValues = [
|
||||
// 2.3,
|
||||
// 1.8,
|
||||
// 1.9,
|
||||
// 1.5,
|
||||
// 1.0,
|
||||
// 2.2,
|
||||
// 1.8,
|
||||
// 1.5,
|
||||
//];
|
15
frontend/lib/models/quick_info_data.dart
Normal file
15
frontend/lib/models/quick_info_data.dart
Normal file
@ -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;
|
||||
}
|
@ -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<String, dynamic> json) {
|
||||
image = json['image'];
|
||||
kBackgroundColor = json['kBackgroundColor'];
|
||||
text = json['text'];
|
||||
altText = json['altText'];
|
||||
bAltText = json['bAltText'];
|
||||
productImage = json['productImage'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
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<SliderModel> 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"
|
||||
}
|
||||
];
|
30
frontend/lib/models/stats.dart
Normal file
30
frontend/lib/models/stats.dart
Normal file
@ -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<String, dynamic> 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});
|
||||
}
|
@ -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",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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]),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<MiniInformationWidget> {
|
||||
@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<Color>? colors;
|
||||
final List<FlSpot>? 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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
78
frontend/lib/screens/dashboard_screen.dart
Normal file
78
frontend/lib/screens/dashboard_screen.dart
Normal file
@ -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<dynamic> 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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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});
|
15
frontend/lib/utils/file_formatter.dart
Normal file
15
frontend/lib/utils/file_formatter.dart
Normal file
@ -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]}';
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user