diff --git a/backend/src/api/backend.rs b/backend/src/api/backend.rs index 1606b81..3511a37 100644 --- a/backend/src/api/backend.rs +++ b/backend/src/api/backend.rs @@ -1,19 +1,7 @@ -use crate::api::list::build_output; -use crate::api::list::okapi_add_operation_for_get_build_; -use crate::api::list::okapi_add_operation_for_stats_; -use crate::api::list::search; -use crate::api::list::{get_build, okapi_add_operation_for_list_builds_}; -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::package::okapi_add_operation_for_package_add_endpoint_; -use crate::api::package::okapi_add_operation_for_package_del_; -use crate::api::package::okapi_add_operation_for_package_list_; -use crate::api::package::okapi_add_operation_for_package_update_endpoint_; -use crate::api::package::package_add_endpoint; -use crate::api::package::{get_package, package_del, package_list}; -use crate::api::package::{okapi_add_operation_for_get_package_, package_update_endpoint}; -use crate::api::remove::okapi_add_operation_for_version_del_; -use crate::api::remove::version_del; +use crate::api::build::*; +use crate::api::list::*; +use crate::api::package::*; +use crate::api::version::*; use rocket::Route; use rocket_okapi::openapi_get_routes; @@ -25,6 +13,7 @@ pub fn build_api() -> Vec { package_del, version_del, build_output, + delete_build, list_builds, stats, get_build, diff --git a/backend/src/api/build.rs b/backend/src/api/build.rs new file mode 100644 index 0000000..0210446 --- /dev/null +++ b/backend/src/api/build.rs @@ -0,0 +1,169 @@ +use crate::db::migration::{JoinType, Order}; +use crate::db::prelude::Builds; +use crate::db::{builds, packages, versions}; +use rocket::response::status::NotFound; +use rocket::serde::json::Json; +use rocket::serde::{Deserialize, Serialize}; +use rocket::{delete, get, State}; +use rocket_okapi::okapi::schemars; +use rocket_okapi::{openapi, JsonSchema}; +use sea_orm::ColumnTrait; +use sea_orm::QueryFilter; +use sea_orm::{ + DatabaseConnection, EntityTrait, FromQueryResult, ModelTrait, QueryOrder, QuerySelect, + RelationTrait, +}; + +#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct ListPackageModel { + id: i32, + name: String, + status: i32, + outofdate: bool, + latest_version: Option, + latest_version_id: Option, + latest_aur_version: String, +} + +#[openapi(tag = "build")] +#[get("/build//output?")] +pub async fn build_output( + db: &State, + buildid: i32, + startline: Option, +) -> Result> { + let db = db as &DatabaseConnection; + + let build = Builds::find_by_id(buildid) + .one(db) + .await + .map_err(|e| NotFound(e.to_string()))? + .ok_or(NotFound("couldn't find id".to_string()))?; + + return match build.ouput { + None => Err(NotFound("No Output".to_string())), + Some(v) => match startline { + None => Ok(v), + Some(startline) => { + let output: Vec = v.split("\n").map(|x| x.to_string()).collect(); + let len = output.len(); + let len_missing = len as i32 - startline; + + let output = output + .iter() + .rev() + .take(if len_missing > 0 { + len_missing as usize + } else { + 0 + }) + .rev() + .map(|x1| x1.clone()) + .collect::>(); + + let output = output.join("\n"); + Ok(output) + } + }, + }; +} + +#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct ListBuildsModel { + id: i32, + pkg_id: i32, + pkg_name: String, + version: String, + status: i32, + start_time: Option, + end_time: Option, +} + +#[openapi(tag = "build")] +#[get("/builds?&")] +pub async fn list_builds( + db: &State, + pkgid: Option, + limit: Option, +) -> Result>, NotFound> { + let db = db as &DatabaseConnection; + + let basequery = Builds::find() + .join_rev(JoinType::InnerJoin, packages::Relation::Builds.def()) + .join_rev(JoinType::InnerJoin, versions::Relation::Builds.def()) + .select_only() + .column_as(builds::Column::Id, "id") + .column(builds::Column::Status) + .column_as(packages::Column::Name, "pkg_name") + .column_as(packages::Column::Id, "pkg_id") + .column(versions::Column::Version) + .column(builds::Column::EndTime) + .column(builds::Column::StartTime) + .order_by(builds::Column::StartTime, Order::Desc) + .limit(limit); + + let build = match pkgid { + None => basequery.into_model::().all(db), + Some(pkgid) => basequery + .filter(builds::Column::PkgId.eq(pkgid)) + .into_model::() + .all(db), + } + .await + .map_err(|e| NotFound(e.to_string()))?; + + Ok(Json(build)) +} + +#[openapi(tag = "build")] +#[get("/build/")] +pub async fn get_build( + db: &State, + buildid: i32, +) -> Result, NotFound> { + let db = db as &DatabaseConnection; + + let result = Builds::find() + .join_rev(JoinType::InnerJoin, packages::Relation::Builds.def()) + .join_rev(JoinType::InnerJoin, versions::Relation::Builds.def()) + .filter(builds::Column::Id.eq(buildid)) + .select_only() + .column_as(builds::Column::Id, "id") + .column(builds::Column::Status) + .column_as(packages::Column::Name, "pkg_name") + .column_as(packages::Column::Id, "pkg_id") + .column(versions::Column::Version) + .column(builds::Column::EndTime) + .column(builds::Column::StartTime) + .into_model::() + .one(db) + .await + .map_err(|e| NotFound(e.to_string()))? + .ok_or(NotFound("no item with id found".to_string()))?; + + Ok(Json(result)) +} + +#[openapi(tag = "build")] +#[delete("/build/")] +pub async fn delete_build( + db: &State, + buildid: i32, +) -> Result<(), NotFound> { + let db = db as &DatabaseConnection; + + let build = Builds::find_by_id(buildid) + .one(db) + .await + .map_err(|e| NotFound(e.to_string()))? + .ok_or(NotFound("Id not found".to_string()))?; + + build + .delete(db) + .await + .map_err(|e| NotFound(e.to_string()))?; + + Ok(()) +} diff --git a/backend/src/api/list.rs b/backend/src/api/list.rs index 68a6e29..7d0fc29 100644 --- a/backend/src/api/list.rs +++ b/backend/src/api/list.rs @@ -1,7 +1,6 @@ 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::db::{builds}; use crate::utils::dir_size::dir_size; use rocket::response::status::NotFound; use rocket::serde::json::Json; @@ -10,8 +9,8 @@ use rocket::{get, State}; use rocket_okapi::okapi::schemars; use rocket_okapi::{openapi, JsonSchema}; use sea_orm::{ColumnTrait, QueryFilter}; -use sea_orm::{DatabaseConnection, EntityTrait, FromQueryResult, QuerySelect, RelationTrait}; -use sea_orm::{Order, PaginatorTrait, QueryOrder}; +use sea_orm::{DatabaseConnection, EntityTrait, FromQueryResult}; +use sea_orm::{PaginatorTrait}; #[derive(Serialize, JsonSchema)] #[serde(crate = "rocket::serde")] @@ -20,7 +19,7 @@ pub struct ApiPackage { version: String, } -#[openapi(tag = "test")] +#[openapi(tag = "aur")] #[get("/search?")] pub async fn search(query: &str) -> Result>, String> { return match query_aur(query).await { @@ -38,135 +37,6 @@ pub async fn search(query: &str) -> Result>, String> { }; } -#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct ListPackageModel { - id: i32, - name: String, - status: i32, - outofdate: bool, - latest_version: Option, - latest_version_id: Option, - latest_aur_version: String, -} - -#[openapi(tag = "test")] -#[get("/builds/output?&")] -pub async fn build_output( - db: &State, - buildid: i32, - startline: Option, -) -> Result> { - let db = db as &DatabaseConnection; - - let build = Builds::find_by_id(buildid) - .one(db) - .await - .map_err(|e| NotFound(e.to_string()))? - .ok_or(NotFound("couldn't find id".to_string()))?; - - return match build.ouput { - None => Err(NotFound("No Output".to_string())), - Some(v) => match startline { - None => Ok(v), - Some(startline) => { - let output: Vec = v.split("\n").map(|x| x.to_string()).collect(); - let len = output.len(); - let len_missing = len as i32 - startline; - - let output = output - .iter() - .rev() - .take(if len_missing > 0 { - len_missing as usize - } else { - 0 - }) - .rev() - .map(|x1| x1.clone()) - .collect::>(); - - let output = output.join("\n"); - Ok(output) - } - }, - }; -} - -#[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct ListBuildsModel { - id: i32, - pkg_name: String, - version: String, - status: i32, - start_time: Option, - end_time: Option, -} - -#[openapi(tag = "test")] -#[get("/builds?&")] -pub async fn list_builds( - db: &State, - pkgid: Option, - limit: Option, -) -> Result>, NotFound> { - let db = db as &DatabaseConnection; - - let basequery = Builds::find() - .join_rev(JoinType::InnerJoin, packages::Relation::Builds.def()) - .join_rev(JoinType::InnerJoin, versions::Relation::Builds.def()) - .select_only() - .column_as(builds::Column::Id, "id") - .column(builds::Column::Status) - .column_as(packages::Column::Name, "pkg_name") - .column(versions::Column::Version) - .column(builds::Column::EndTime) - .column(builds::Column::StartTime) - .order_by(builds::Column::StartTime, Order::Desc) - .limit(limit); - - let build = match pkgid { - None => basequery.into_model::().all(db), - Some(pkgid) => basequery - .filter(builds::Column::PkgId.eq(pkgid)) - .into_model::() - .all(db), - } - .await - .map_err(|e| NotFound(e.to_string()))?; - - Ok(Json(build)) -} - -#[openapi(tag = "test")] -#[get("/builds/")] -pub async fn get_build( - db: &State, - buildid: i32, -) -> Result, NotFound> { - let db = db as &DatabaseConnection; - - let result = Builds::find() - .join_rev(JoinType::InnerJoin, packages::Relation::Builds.def()) - .join_rev(JoinType::InnerJoin, versions::Relation::Builds.def()) - .filter(builds::Column::Id.eq(buildid)) - .select_only() - .column_as(builds::Column::Id, "id") - .column(builds::Column::Status) - .column_as(packages::Column::Name, "pkg_name") - .column(versions::Column::Version) - .column(builds::Column::EndTime) - .column(builds::Column::StartTime) - .into_model::() - .one(db) - .await - .map_err(|e| NotFound(e.to_string()))? - .ok_or(NotFound("no item with id found".to_string()))?; - - Ok(Json(result)) -} - #[derive(FromQueryResult, Deserialize, JsonSchema, Serialize)] #[serde(crate = "rocket::serde")] pub struct ListStats { @@ -179,15 +49,15 @@ pub struct ListStats { total_packages: u32, } -#[openapi(tag = "test")] +#[openapi(tag = "stats")] #[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())), - }; + get_stats(db) + .await + .map_err(|e| NotFound(e.to_string())) + .map(Json) } async fn get_stats(db: &DatabaseConnection) -> anyhow::Result { diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 1721aac..8df1443 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,6 +1,7 @@ pub mod backend; +mod build; #[cfg(feature = "static")] pub mod embed; mod list; mod package; -mod remove; +mod version; diff --git a/backend/src/api/package.rs b/backend/src/api/package.rs index 78a609e..f642219 100644 --- a/backend/src/api/package.rs +++ b/backend/src/api/package.rs @@ -1,4 +1,4 @@ -use crate::api::list::ListPackageModel; +use crate::api::build::ListPackageModel; use crate::builder::types::Action; use crate::db::migration::{JoinType, Order}; use crate::db::prelude::Packages; @@ -9,7 +9,7 @@ use crate::package::update::package_update; use rocket::response::status::{BadRequest, NotFound}; use rocket::serde::json::Json; use rocket::serde::Deserialize; -use rocket::{get, post, State}; +use rocket::{delete, get, post, State}; use rocket_okapi::okapi::schemars; use rocket_okapi::{openapi, JsonSchema}; use sea_orm::DatabaseConnection; @@ -23,7 +23,7 @@ pub struct AddBody { } #[openapi(tag = "Packages")] -#[post("/packages/add", data = "")] +#[post("/package", data = "")] pub async fn package_add_endpoint( db: &State, input: Json, @@ -41,20 +41,21 @@ pub struct UpdateBody { } #[openapi(tag = "Packages")] -#[post("/packages//update", data = "")] +#[post("/package//update", data = "")] pub async fn package_update_endpoint( db: &State, id: i32, input: Json, tx: &State>, -) -> Result<(), BadRequest> { +) -> Result, BadRequest> { package_update(db, id, input.force, tx) .await + .map(|e| Json(e)) .map_err(|e| BadRequest(Some(e.to_string()))) } #[openapi(tag = "Packages")] -#[post("/package/delete/")] +#[delete("/package/")] pub async fn package_del(db: &State, id: i32) -> Result<(), String> { let db = db as &DatabaseConnection; diff --git a/backend/src/api/remove.rs b/backend/src/api/version.rs similarity index 77% rename from backend/src/api/remove.rs rename to backend/src/api/version.rs index 08b4048..844a130 100644 --- a/backend/src/api/remove.rs +++ b/backend/src/api/version.rs @@ -1,10 +1,10 @@ use crate::repo::repo::remove_version; -use rocket::{post, State}; +use rocket::{delete, State}; use rocket_okapi::openapi; use sea_orm::DatabaseConnection; -#[openapi(tag = "test")] -#[post("/versions/delete/")] +#[openapi(tag = "version")] +#[delete("/version//delete")] pub async fn version_del(db: &State, id: i32) -> Result<(), String> { let db = db as &DatabaseConnection; diff --git a/backend/src/builder/builder.rs b/backend/src/builder/builder.rs index 560af79..99cf3f7 100644 --- a/backend/src/builder/builder.rs +++ b/backend/src/builder/builder.rs @@ -19,12 +19,13 @@ pub async fn init(db: DatabaseConnection, tx: Sender) { if let Ok(_result) = tx.subscribe().recv().await { match _result { // add a package to parallel build - Action::Build(name, version, url, version_model) => { + Action::Build(name, version, url, version_model, build_model) => { let _ = queue_package( name, version, url, version_model, + build_model, db.clone(), semaphore.clone(), ) @@ -40,25 +41,10 @@ async fn queue_package( version: String, url: String, version_model: versions::ActiveModel, + mut build_model: builds::ActiveModel, db: DatabaseConnection, semaphore: Arc, ) -> anyhow::Result<()> { - // set build status to pending - let build = builds::ActiveModel { - pkg_id: version_model.package_id.clone(), - version_id: version_model.id.clone(), - ouput: Set(None), - status: Set(Some(3)), - start_time: Set(Some( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() as u32, - )), - ..Default::default() - }; - let mut new_build = build.save(&db).await.unwrap(); - let permits = Arc::clone(&semaphore); // spawn new thread for each pkg build @@ -67,10 +53,10 @@ async fn queue_package( let _permit = permits.acquire().await.unwrap(); // set build status to building - new_build.status = Set(Some(0)); - new_build = new_build.save(&db).await.unwrap(); + build_model.status = Set(Some(0)); + build_model = build_model.save(&db).await.unwrap(); - let _ = build_package(new_build, db, version_model, version, name, url).await; + let _ = build_package(build_model, db, version_model, version, name, url).await; }); Ok(()) } diff --git a/backend/src/builder/types.rs b/backend/src/builder/types.rs index 84eea5c..8c94fd7 100644 --- a/backend/src/builder/types.rs +++ b/backend/src/builder/types.rs @@ -1,6 +1,12 @@ -use crate::db::versions; +use crate::db::{builds, versions}; #[derive(Clone)] pub enum Action { - Build(String, String, String, versions::ActiveModel), + Build( + String, + String, + String, + versions::ActiveModel, + builds::ActiveModel, + ), } diff --git a/backend/src/package/add.rs b/backend/src/package/add.rs index a86aef5..e97404e 100644 --- a/backend/src/package/add.rs +++ b/backend/src/package/add.rs @@ -1,11 +1,12 @@ use crate::aur::aur::get_info_by_name; use crate::builder::types::Action; use crate::db::prelude::Packages; -use crate::db::{packages, versions}; +use crate::db::{builds, packages, versions}; use anyhow::anyhow; use sea_orm::ColumnTrait; use sea_orm::QueryFilter; use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, Set, TransactionTrait}; +use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::broadcast::Sender; pub async fn package_add( @@ -49,11 +50,28 @@ pub async fn package_add( new_package.latest_version_id = Set(Some(new_version.id.clone().unwrap())); new_package.save(&txn).await?; + // set build status to pending + let build = builds::ActiveModel { + pkg_id: new_version.package_id.clone(), + version_id: new_version.id.clone(), + ouput: Set(None), + status: Set(Some(3)), + start_time: Set(Some( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as u32, + )), + ..Default::default() + }; + let new_build = build.save(&txn).await?; + let _ = tx.send(Action::Build( pkg.name, pkg.version, pkg.url_path.unwrap(), new_version, + new_build, )); txn.commit().await?; diff --git a/backend/src/package/update.rs b/backend/src/package/update.rs index ed268af..fface1e 100644 --- a/backend/src/package/update.rs +++ b/backend/src/package/update.rs @@ -1,11 +1,12 @@ use crate::aur::aur::get_info_by_name; use crate::builder::types::Action; use crate::db::prelude::{Packages, Versions}; -use crate::db::{packages, versions}; +use crate::db::{builds, packages, versions}; use anyhow::anyhow; use sea_orm::ColumnTrait; use sea_orm::QueryFilter; use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, Set, TransactionTrait}; +use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::broadcast::Sender; pub async fn package_update( @@ -13,7 +14,7 @@ pub async fn package_update( pkg_id: i32, force: bool, tx: &Sender, -) -> anyhow::Result<()> { +) -> anyhow::Result { let txn = db.begin().await?; let mut pkg_model: packages::ActiveModel = Packages::find_by_id(pkg_id) @@ -54,16 +55,34 @@ pub async fn package_update( pkg_model.status = Set(3); pkg_model.latest_version_id = Set(Some(version_model.id.clone().unwrap())); - pkg_model.save(&txn).await.expect("todo error message"); + pkg_model.save(&txn).await?; + + // set build status to pending + let build = builds::ActiveModel { + pkg_id: version_model.package_id.clone(), + version_id: version_model.id.clone(), + ouput: Set(None), + status: Set(Some(3)), + start_time: Set(Some( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as u32, + )), + ..Default::default() + }; + let new_build = build.save(&txn).await?; + let build_id = new_build.id.clone().unwrap(); let _ = tx.send(Action::Build( pkg.name, pkg.version, pkg.url_path.unwrap(), version_model, + new_build, )); txn.commit().await?; - Ok(()) + Ok(build_id) } diff --git a/frontend/lib/api/api_client.dart b/frontend/lib/api/api_client.dart index bcea026..dd0e9d8 100644 --- a/frontend/lib/api/api_client.dart +++ b/frontend/lib/api/api_client.dart @@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart'; class ApiClient { static const String _apiBase = - kDebugMode ? "https://aurcache.heili.eu/api" : "api"; + kDebugMode ? "http://localhost:8081/api" : "api"; final Dio _dio = Dio(BaseOptions(baseUrl: _apiBase)); String? token; diff --git a/frontend/lib/api/builds.dart b/frontend/lib/api/builds.dart index d90eb2a..4c3bd41 100644 --- a/frontend/lib/api/builds.dart +++ b/frontend/lib/api/builds.dart @@ -21,14 +21,19 @@ extension BuildsAPI on ApiClient { } Future getBuild(int id) async { - final resp = await getRawClient().get("/builds/${id}"); + final resp = await getRawClient().get("/build/${id}"); return Build.fromJson(resp.data); } + Future deleteBuild(int id) async { + final resp = await getRawClient().delete("/build/${id}"); + return resp.statusCode == 400; + } + Future getOutput({int? line, required int buildID}) async { - String uri = "/builds/output?buildid=$buildID"; + String uri = "/build/$buildID/output"; if (line != null) { - uri += "&startline=$line"; + uri += "?startline=$line"; } final resp = await getRawClient().get(uri); return resp.data.toString(); diff --git a/frontend/lib/api/packages.dart b/frontend/lib/api/packages.dart index a0cd7c0..27f98a6 100644 --- a/frontend/lib/api/packages.dart +++ b/frontend/lib/api/packages.dart @@ -20,19 +20,20 @@ extension PackagesAPI on ApiClient { } Future addPackage({required String name}) async { - final resp = - await getRawClient().post("/packages/add", data: {'name': name}); + final resp = await getRawClient().post("/package", data: {'name': name}); print(resp.data); } - Future updatePackage({bool force = false, required int id}) async { + Future updatePackage({bool force = false, required int id}) async { final resp = await getRawClient() - .post("/packages/$id/update", data: {'force': force}); + .post("/package/$id/update", data: {'force': force}); print(resp.data); + + return resp.data as int; } Future deletePackage(int id) async { - final resp = await getRawClient().post("/package/delete/$id"); + final resp = await getRawClient().delete("/package/$id"); return resp.statusCode == 200; } } diff --git a/frontend/lib/models/build.dart b/frontend/lib/models/build.dart index bf839aa..d534230 100644 --- a/frontend/lib/models/build.dart +++ b/frontend/lib/models/build.dart @@ -1,12 +1,14 @@ class Build { final int id; final String pkg_name; + final int pkg_id; final String version; final int status; final int? start_time, end_time; Build( {required this.id, + required this.pkg_id, required this.pkg_name, required this.version, required this.start_time, @@ -16,6 +18,7 @@ class Build { factory Build.fromJson(Map json) { return Build( id: json["id"] as int, + pkg_id: json["pkg_id"] as int, status: json["status"] as int, start_time: json["start_time"] as int?, end_time: json["end_time"] as int?, diff --git a/frontend/lib/screens/build_screen.dart b/frontend/lib/screens/build_screen.dart index abdc135..c199b2d 100644 --- a/frontend/lib/screens/build_screen.dart +++ b/frontend/lib/screens/build_screen.dart @@ -1,3 +1,5 @@ +import 'package:aurcache/api/builds.dart'; +import 'package:aurcache/api/packages.dart'; import 'package:aurcache/components/build_output.dart'; import 'package:aurcache/models/build.dart'; import 'package:aurcache/components/api/APIBuilder.dart'; @@ -7,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import '../api/API.dart'; import '../components/confirm_popup.dart'; import '../components/dashboard/chart_card.dart'; import '../components/dashboard/your_packages.dart'; @@ -57,7 +60,7 @@ class _BuildScreenState extends State { ], ), ), - _buildSideBar(), + _buildSideBar(buildData), ], ); }); @@ -159,7 +162,7 @@ class _BuildScreenState extends State { ); } - Widget _buildSideBar() { + Widget _buildSideBar(Build buildData) { return SizedBox( width: 300, child: Container( @@ -190,8 +193,9 @@ class _BuildScreenState extends State { final confirmResult = await showConfirmationDialog( context, "Delete Build", - "Are you sure to delete this Package?", () async { - // todo delete build + "Are you sure to delete this Package?", () { + API.deleteBuild(widget.buildID); + context.pop(); }, null); }, child: const Text( @@ -204,7 +208,9 @@ class _BuildScreenState extends State { ), ElevatedButton( onPressed: () async { - // todo api call and page redirect + final buildid = + await API.updatePackage(id: buildData.pkg_id); + context.pushReplacement("/build/$buildid"); }, child: const Text( "Retry", @@ -220,21 +226,21 @@ class _BuildScreenState extends State { const SizedBox( height: 5, ), - Text( + const Text( "Build Information:", style: TextStyle(fontSize: 18), textAlign: TextAlign.start, ), - SizedBox( + const SizedBox( height: 20, ), SideCard( title: "Build Number", - textRight: "7", + textRight: buildData.id.toString(), ), SideCard( title: "Finished", - textRight: "7", + textRight: buildData.end_time.toString(), ), SideCard( title: "Queued", @@ -242,7 +248,13 @@ class _BuildScreenState extends State { ), SideCard( title: "Duration", - textRight: "7", + textRight: (buildData.end_time != null + ? DateTime.fromMillisecondsSinceEpoch( + buildData.end_time! * 1000) + : DateTime.now()) + .difference(DateTime.fromMillisecondsSinceEpoch( + buildData.start_time! * 1000)) + .readableDuration(), ), ], ), diff --git a/frontend/lib/utils/time_formatter.dart b/frontend/lib/utils/time_formatter.dart index 14e714c..301cf71 100644 --- a/frontend/lib/utils/time_formatter.dart +++ b/frontend/lib/utils/time_formatter.dart @@ -18,3 +18,21 @@ extension TimeFormatter on DateTime { } } } + +extension DurationFormatter on Duration { + String readableDuration() { + if (inSeconds < 60) { + return '$inSeconds second${inSeconds != 1 ? 's' : ''}'; + } else if (inMinutes < 60) { + return '$inMinutes minute${inMinutes != 1 ? 's' : ''}'; + } else if (inHours < 24) { + return '$inHours hour${inHours != 1 ? 's' : ''}'; + } else if (inDays < 30) { + return '$inDays day${inDays != 1 ? 's' : ''}'; + } else if ((inDays / 30) < 12) { + return '${inDays ~/ 30} month${(inDays ~/ 30) != 1 ? 's' : ''}'; + } else { + return '${inDays ~/ 365} year${(inDays ~/ 365) != 1 ? 's' : ''}'; + } + } +}