forked from github-starred/komodo
add status to build / repo
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use async_trait::async_trait;
|
||||
@@ -23,7 +23,7 @@ use monitor_client::{
|
||||
};
|
||||
use mungos::{
|
||||
find::find_collect,
|
||||
mongodb::bson::{doc, oid::ObjectId, to_bson},
|
||||
mongodb::bson::{doc, to_bson},
|
||||
};
|
||||
use periphery_client::{
|
||||
api::{self, GetVersionResponse},
|
||||
@@ -223,7 +223,7 @@ impl Resolve<RunBuild, User> for State {
|
||||
.await
|
||||
.builds
|
||||
.update_one(
|
||||
doc! { "_id": ObjectId::from_str(&build.id)? },
|
||||
doc! { "name": &build.name },
|
||||
doc! {
|
||||
"$set": {
|
||||
"config.version": to_bson(&build.config.version)
|
||||
@@ -352,11 +352,8 @@ async fn get_aws_builder(
|
||||
) -> anyhow::Result<(PeripheryClient, BuildCleanupData)> {
|
||||
let start_create_ts = monitor_timestamp();
|
||||
|
||||
let instance_name = format!(
|
||||
"BUILDER-{}-v{}",
|
||||
build.name,
|
||||
build.config.version
|
||||
);
|
||||
let instance_name =
|
||||
format!("BUILDER-{}-v{}", build.name, build.config.version);
|
||||
let Ec2Instance { instance_id, ip } = launch_ec2_instance(
|
||||
&instance_name,
|
||||
AwsServerTemplateConfig::from_builder_config(&config),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use monitor_client::{
|
||||
@@ -14,7 +12,7 @@ use monitor_client::{
|
||||
Operation,
|
||||
},
|
||||
};
|
||||
use mungos::mongodb::bson::{doc, oid::ObjectId};
|
||||
use mungos::mongodb::bson::doc;
|
||||
use periphery_client::api;
|
||||
use resolver_api::Resolve;
|
||||
use serror::serialize_error_pretty;
|
||||
@@ -98,20 +96,7 @@ impl Resolve<CloneRepo, User> for State {
|
||||
update.finalize();
|
||||
|
||||
if update.success {
|
||||
let res = db_client().await
|
||||
.repos
|
||||
.update_one(
|
||||
doc! { "_id": ObjectId::from_str(&repo.id)? },
|
||||
doc! { "$set": { "info.last_pulled_at": monitor_timestamp() } },
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"failed to update repo last_pulled_at | repo id: {} | {e:#}",
|
||||
repo.id
|
||||
);
|
||||
}
|
||||
update_last_pulled(&repo.name).await;
|
||||
}
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
@@ -168,7 +153,7 @@ impl Resolve<PullRepo, User> for State {
|
||||
|
||||
let logs = match periphery
|
||||
.request(api::git::PullRepo {
|
||||
name: repo.name,
|
||||
name: repo.name.clone(),
|
||||
branch: optional_string(&repo.config.branch),
|
||||
on_pull: repo.config.on_pull.into_option(),
|
||||
})
|
||||
@@ -185,23 +170,27 @@ impl Resolve<PullRepo, User> for State {
|
||||
update.finalize();
|
||||
|
||||
if update.success {
|
||||
let res = db_client().await
|
||||
.repos
|
||||
.update_one(
|
||||
doc! { "_id": ObjectId::from_str(&repo.id)? },
|
||||
doc! { "$set": { "info.last_pulled_at": monitor_timestamp() } },
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"failed to update repo last_pulled_at | repo id: {} | {e:#}",
|
||||
repo.id
|
||||
);
|
||||
}
|
||||
update_last_pulled(&repo.name).await;
|
||||
}
|
||||
|
||||
update_update(update.clone()).await?;
|
||||
Ok(update)
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_last_pulled(repo_name: &str) {
|
||||
let res = db_client()
|
||||
.await
|
||||
.repos
|
||||
.update_one(
|
||||
doc! { "name": repo_name },
|
||||
doc! { "$set": { "info.last_pulled_at": monitor_timestamp() } },
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
warn!(
|
||||
"failed to update repo last_pulled_at | repo: {repo_name} | {e:#}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
build::BuildStatus,
|
||||
deployment::{Deployment, DockerContainerState},
|
||||
permission::PermissionLevel,
|
||||
repo::RepoStatus,
|
||||
server::{Server, ServerStatus},
|
||||
tag::Tag,
|
||||
update::ResourceTargetVariant,
|
||||
@@ -15,7 +17,10 @@ use mungos::{
|
||||
mongodb::bson::{doc, oid::ObjectId, Document},
|
||||
};
|
||||
|
||||
use crate::{resource, state::db_client};
|
||||
use crate::{
|
||||
resource,
|
||||
state::{action_states, db_client},
|
||||
};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_user(user_id: &str) -> anyhow::Result<User> {
|
||||
@@ -198,3 +203,98 @@ pub fn id_or_name_filter(id_or_name: &str) -> Document {
|
||||
Err(_) => doc! { "name": id_or_name },
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_build_status(id: &String) -> BuildStatus {
|
||||
async {
|
||||
if action_states()
|
||||
.build
|
||||
.get(id)
|
||||
.await
|
||||
.map(|s| s.get().map(|s| s.building))
|
||||
.transpose()?
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok(BuildStatus::Building);
|
||||
}
|
||||
let status = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "Build",
|
||||
"target.id": id,
|
||||
"operation": "RunBuild"
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
BuildStatus::Ok
|
||||
} else {
|
||||
BuildStatus::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(BuildStatus::Ok);
|
||||
anyhow::Ok(status)
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to get build status for {id} | {e:#}")
|
||||
})
|
||||
.unwrap_or(BuildStatus::Unknown)
|
||||
}
|
||||
|
||||
pub async fn get_repo_status(id: &String) -> RepoStatus {
|
||||
async {
|
||||
if let Some(status) = action_states()
|
||||
.repo
|
||||
.get(id)
|
||||
.await
|
||||
.map(|s| {
|
||||
s.get().map(|s| {
|
||||
if s.cloning {
|
||||
Some(RepoStatus::Cloning)
|
||||
} else if s.pulling {
|
||||
Some(RepoStatus::Pulling)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.flatten()
|
||||
{
|
||||
return Ok(status);
|
||||
}
|
||||
let status = db_client()
|
||||
.await
|
||||
.updates
|
||||
.find_one(
|
||||
doc! {
|
||||
"target.type": "Repo",
|
||||
"target.id": id,
|
||||
"$or": [
|
||||
{ "operation": "CloneRepo" },
|
||||
{ "operation": "PullRepo" },
|
||||
],
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.map(|u| {
|
||||
if u.success {
|
||||
RepoStatus::Ok
|
||||
} else {
|
||||
RepoStatus::Failed
|
||||
}
|
||||
})
|
||||
.unwrap_or(RepoStatus::Ok);
|
||||
anyhow::Ok(status)
|
||||
}
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to get repo status for {id} | {e:#}")
|
||||
})
|
||||
.unwrap_or(RepoStatus::Unknown)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use monitor_client::entities::{
|
||||
use mungos::mongodb::Collection;
|
||||
|
||||
use crate::{
|
||||
helpers::empty_or_only_spaces,
|
||||
helpers::{empty_or_only_spaces, query::get_build_status},
|
||||
state::{action_states, db_client},
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ impl super::MonitorResource for Build {
|
||||
async fn to_list_item(
|
||||
build: Resource<Self::Config, Self::Info>,
|
||||
) -> anyhow::Result<Self::ListItem> {
|
||||
let status = get_build_status(&build.id).await;
|
||||
Ok(BuildListItem {
|
||||
name: build.name,
|
||||
id: build.id,
|
||||
@@ -48,6 +49,7 @@ impl super::MonitorResource for Build {
|
||||
version: build.config.version,
|
||||
repo: build.config.repo,
|
||||
branch: build.config.branch,
|
||||
status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use periphery_client::api::git::DeleteRepo;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{
|
||||
helpers::periphery_client,
|
||||
helpers::{periphery_client, query::get_repo_status},
|
||||
state::{action_states, db_client},
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ impl super::MonitorResource for Repo {
|
||||
async fn to_list_item(
|
||||
repo: Resource<Self::Config, Self::Info>,
|
||||
) -> anyhow::Result<Self::ListItem> {
|
||||
let status = get_repo_status(&repo.id).await;
|
||||
Ok(RepoListItem {
|
||||
name: repo.name,
|
||||
id: repo.id,
|
||||
@@ -51,6 +52,7 @@ impl super::MonitorResource for Repo {
|
||||
last_pulled_at: repo.info.last_pulled_at,
|
||||
repo: repo.config.repo,
|
||||
branch: repo.config.branch,
|
||||
status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use derive_default_builder::DefaultBuilder;
|
||||
use mungos::mongodb::bson::{doc, Document};
|
||||
use partial_derive2::Partial;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::I64;
|
||||
@@ -19,7 +20,7 @@ pub type Build = Resource<BuildConfig, BuildInfo>;
|
||||
pub type BuildListItem = ResourceListItem<BuildListItemInfo>;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuildListItemInfo {
|
||||
/// Unix timestamp in milliseconds of last build
|
||||
pub last_built_at: I64,
|
||||
@@ -29,6 +30,24 @@ pub struct BuildListItemInfo {
|
||||
pub repo: String,
|
||||
/// The branch of the repo
|
||||
pub branch: String,
|
||||
/// Status for the build
|
||||
pub status: BuildStatus,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, Serialize, Deserialize, Display,
|
||||
)]
|
||||
pub enum BuildStatus {
|
||||
/// Last build successful (or never built)
|
||||
Ok,
|
||||
/// Last build failed
|
||||
Failed,
|
||||
/// Currently building
|
||||
Building,
|
||||
/// Other case
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -3,6 +3,7 @@ use derive_default_builder::DefaultBuilder;
|
||||
use mungos::mongodb::bson::{doc, Document};
|
||||
use partial_derive2::Partial;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::I64;
|
||||
@@ -18,9 +19,32 @@ pub type RepoListItem = ResourceListItem<RepoListItemInfo>;
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct RepoListItemInfo {
|
||||
/// Repo last cloned / pulled timestamp in ms.
|
||||
pub last_pulled_at: I64,
|
||||
/// The configured github repo
|
||||
pub repo: String,
|
||||
/// The configured branch
|
||||
pub branch: String,
|
||||
/// The repo status
|
||||
pub status: RepoStatus,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, Serialize, Deserialize, Display,
|
||||
)]
|
||||
pub enum RepoStatus {
|
||||
/// Last clone / pull successful (or never cloned)
|
||||
Ok,
|
||||
/// Last clone / pull failed
|
||||
Failed,
|
||||
/// Currently cloning
|
||||
Cloning,
|
||||
/// Currently pullling
|
||||
Pulling,
|
||||
/// Other case
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
@@ -29,6 +53,7 @@ pub type Repo = Resource<RepoConfig, RepoInfo>;
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct RepoInfo {
|
||||
/// When repo was last pulled
|
||||
pub last_pulled_at: I64,
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,17 @@ export type Build = Resource<BuildConfig, BuildInfo>;
|
||||
|
||||
export type GetBuildResponse = Build;
|
||||
|
||||
export enum BuildStatus {
|
||||
/** Last build successful (or never built) */
|
||||
Ok = "Ok",
|
||||
/** Last build failed */
|
||||
Failed = "Failed",
|
||||
/** Currently building */
|
||||
Building = "Building",
|
||||
/** Other case */
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
export interface BuildListItemInfo {
|
||||
/** Unix timestamp in milliseconds of last build */
|
||||
last_built_at: I64;
|
||||
@@ -338,6 +349,8 @@ export interface BuildListItemInfo {
|
||||
repo: string;
|
||||
/** The branch of the repo */
|
||||
branch: string;
|
||||
/** Status for the build */
|
||||
status: BuildStatus;
|
||||
}
|
||||
|
||||
export type BuildListItem = ResourceListItem<BuildListItemInfo>;
|
||||
@@ -679,6 +692,7 @@ export interface RepoConfig {
|
||||
}
|
||||
|
||||
export interface RepoInfo {
|
||||
/** When repo was last pulled */
|
||||
last_pulled_at: I64;
|
||||
}
|
||||
|
||||
@@ -686,10 +700,28 @@ export type Repo = Resource<RepoConfig, RepoInfo>;
|
||||
|
||||
export type GetRepoResponse = Repo;
|
||||
|
||||
export enum RepoStatus {
|
||||
/** Last clone / pull successful (or never cloned) */
|
||||
Ok = "Ok",
|
||||
/** Last clone / pull failed */
|
||||
Failed = "Failed",
|
||||
/** Currently cloning */
|
||||
Cloning = "Cloning",
|
||||
/** Currently pullling */
|
||||
Pulling = "Pulling",
|
||||
/** Other case */
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
export interface RepoListItemInfo {
|
||||
/** Repo last cloned / pulled timestamp in ms. */
|
||||
last_pulled_at: I64;
|
||||
/** The configured github repo */
|
||||
repo: string;
|
||||
/** The configured branch */
|
||||
branch: string;
|
||||
/** The repo status */
|
||||
status: RepoStatus;
|
||||
}
|
||||
|
||||
export type RepoListItem = ResourceListItem<RepoListItemInfo>;
|
||||
|
||||
@@ -9,6 +9,9 @@ import { DeleteResource, NewResource } from "../common";
|
||||
import { DeploymentTable } from "../deployment/table";
|
||||
import { RunBuild } from "./actions";
|
||||
import { IconStrictId } from "./icon";
|
||||
import { bg_color_class_by_intention, build_status_intention } from "@lib/color";
|
||||
import { Card, CardHeader } from "@ui/card";
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
const useBuild = (id?: string) =>
|
||||
useRead("ListBuilds", {}).data?.find((d) => d.id === id);
|
||||
@@ -28,7 +31,21 @@ export const BuildComponents: RequiredResourceComponents = {
|
||||
else return <Hammer className="w-4 h-4" />;
|
||||
},
|
||||
|
||||
Status: {},
|
||||
Status: {
|
||||
Status: ({ id }) => {
|
||||
let status = useBuild(id)?.info.status;
|
||||
const color = bg_color_class_by_intention(
|
||||
build_status_intention(status)
|
||||
);
|
||||
return (
|
||||
<Card className={cn("w-fit", color)}>
|
||||
<CardHeader className="py-0 px-2">
|
||||
{status}
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
Info: {
|
||||
Repo: ({ id }) => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { fmt_date_with_minutes, fmt_version } from "@lib/formatting";
|
||||
import { fmt_version } from "@lib/formatting";
|
||||
import { ResourceLink } from "../common";
|
||||
import { BuildComponents } from ".";
|
||||
|
||||
export const BuildTable = ({ search }: { search?: string }) => {
|
||||
const builds = useRead("ListBuilds", {}).data;
|
||||
@@ -39,17 +40,13 @@ export const BuildTable = ({ search }: { search?: string }) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "info.last_built_at",
|
||||
accessorKey: "info.status",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Last Built" />
|
||||
<SortableHeader column={column} title="Status" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<BuildComponents.Status.Status id={row.original.id} />
|
||||
),
|
||||
accessorFn: ({ info: { last_built_at } }) => {
|
||||
if (last_built_at > 0) {
|
||||
return fmt_date_with_minutes(new Date(last_built_at));
|
||||
} else {
|
||||
return "never";
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { RepoConfig } from "./config";
|
||||
import { CloneRepo, PullRepo } from "./actions";
|
||||
import { DeleteResource, NewResource } from "../common";
|
||||
import { RepoTable } from "./table";
|
||||
import { bg_color_class_by_intention, repo_status_intention } from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
const useRepo = (id?: string) =>
|
||||
useRead("ListRepos", {}).data?.find((d) => d.id === id);
|
||||
@@ -40,7 +42,17 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
|
||||
Icon: () => <GitBranch className="w-4 h-4" />,
|
||||
|
||||
Status: {},
|
||||
Status: {
|
||||
Status: ({ id }) => {
|
||||
let status = useRepo(id)?.info.status;
|
||||
const color = bg_color_class_by_intention(repo_status_intention(status));
|
||||
return (
|
||||
<Card className={cn("w-fit", color)}>
|
||||
<CardHeader className="py-0 px-2">{status}</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
Info: {
|
||||
Repo: ({ id }) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { DataTable, SortableHeader } from "@ui/data-table";
|
||||
import { ResourceLink } from "../common";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { RepoComponents } from ".";
|
||||
|
||||
export const RepoTable = ({ search }: { search?: string }) => {
|
||||
const tags = useTagsFilter();
|
||||
@@ -39,6 +40,15 @@ export const RepoTable = ({ search }: { search?: string }) => {
|
||||
<SortableHeader column={column} title="Branch" />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "info.status",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Status" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<RepoComponents.Status.Status id={row.original.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
cell: ({ row }) => {
|
||||
|
||||
@@ -118,6 +118,42 @@ export const deployment_state_intention: (
|
||||
}
|
||||
};
|
||||
|
||||
export const build_status_intention = (status?: Types.BuildStatus) => {
|
||||
switch (status) {
|
||||
case undefined:
|
||||
return "None";
|
||||
case Types.BuildStatus.Unknown:
|
||||
return "Unknown";
|
||||
case Types.BuildStatus.Ok:
|
||||
return "Good";
|
||||
case Types.BuildStatus.Building:
|
||||
return "Warning";
|
||||
case Types.BuildStatus.Failed:
|
||||
return "Critical";
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
export const repo_status_intention = (status?: Types.RepoStatus) => {
|
||||
switch (status) {
|
||||
case undefined:
|
||||
return "None";
|
||||
case Types.RepoStatus.Unknown:
|
||||
return "Unknown";
|
||||
case Types.RepoStatus.Ok:
|
||||
return "Good";
|
||||
case Types.RepoStatus.Cloning:
|
||||
return "Warning";
|
||||
case Types.RepoStatus.Pulling:
|
||||
return "Warning";
|
||||
case Types.RepoStatus.Failed:
|
||||
return "Critical";
|
||||
default:
|
||||
return "None";
|
||||
}
|
||||
};
|
||||
|
||||
export const alert_level_intention: (
|
||||
level: Types.SeverityLevel
|
||||
) => ColorIntention = (level) => {
|
||||
|
||||
Reference in New Issue
Block a user