diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 8280c058f..639b7b3a6 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -7,12 +7,11 @@ use monitor_client::{ entities::{ alerter::{Alerter, AlerterListItem}, permission::PermissionLevel, - resource::AddFilters, update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ @@ -46,9 +45,7 @@ impl Resolve for State { ListAlerters { query }: ListAlerters, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Alerter::list_resources_for_user(filters, &user).await + Alerter::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/build.rs b/bin/core/src/api/read/build.rs index df21aed5e..a5f8cf6ec 100644 --- a/bin/core/src/api/read/build.rs +++ b/bin/core/src/api/read/build.rs @@ -9,7 +9,6 @@ use monitor_client::{ entities::{ build::{Build, BuildActionState, BuildListItem}, permission::PermissionLevel, - resource::AddFilters, update::{ResourceTargetVariant, UpdateStatus}, user::User, Operation, @@ -18,7 +17,7 @@ use monitor_client::{ use mungos::{ find::find_collect, mongodb::{ - bson::{doc, oid::ObjectId, Document}, + bson::{doc, oid::ObjectId}, options::FindOptions, }, }; @@ -56,9 +55,7 @@ impl Resolve for State { ListBuilds { query }: ListBuilds, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Build::list_resources_for_user(filters, &user).await + Build::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/builder.rs b/bin/core/src/api/read/builder.rs index c7e08fcdd..96cbe012f 100644 --- a/bin/core/src/api/read/builder.rs +++ b/bin/core/src/api/read/builder.rs @@ -7,12 +7,11 @@ use monitor_client::{ entities::{ builder::{Builder, BuilderConfig, BuilderListItem}, permission::PermissionLevel, - resource::AddFilters, update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ @@ -46,9 +45,7 @@ impl Resolve for State { ListBuilders { query }: ListBuilders, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Builder::list_resources_for_user(filters, &user).await + Builder::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/deployment.rs b/bin/core/src/api/read/deployment.rs index 3e75ffe3b..9b508d92d 100644 --- a/bin/core/src/api/read/deployment.rs +++ b/bin/core/src/api/read/deployment.rs @@ -11,7 +11,6 @@ use monitor_client::{ DockerContainerStats, }, permission::PermissionLevel, - resource::AddFilters, server::Server, update::{Log, ResourceTargetVariant, UpdateStatus}, user::User, @@ -21,7 +20,7 @@ use monitor_client::{ use mungos::{ find::find_collect, mongodb::{ - bson::{doc, oid::ObjectId, Document}, + bson::{doc, oid::ObjectId}, options::FindOneOptions, }, }; @@ -61,9 +60,7 @@ impl Resolve for State { ListDeployments { query }: ListDeployments, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Deployment::list_resources_for_user(filters, &user).await + Deployment::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index b4f4467cc..36157e367 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -48,7 +48,6 @@ enum ReadRequest { GetProcedure(GetProcedure), GetProcedureActionState(GetProcedureActionState), ListProcedures(ListProcedures), - ListProceduresByIds(ListProceduresByIds), // ==== SERVER ==== GetServersSummary(GetServersSummary), diff --git a/bin/core/src/api/read/procedure.rs b/bin/core/src/api/read/procedure.rs index 356bba616..b7a13203a 100644 --- a/bin/core/src/api/read/procedure.rs +++ b/bin/core/src/api/read/procedure.rs @@ -7,15 +7,14 @@ use monitor_client::{ GetProcedure, GetProcedureActionState, GetProcedureActionStateResponse, GetProcedureResponse, GetProceduresSummary, GetProceduresSummaryResponse, - ListProcedures, ListProceduresByIds, ListProceduresByIdsResponse, - ListProceduresResponse, + ListProcedures, ListProceduresResponse, }, entities::{ permission::PermissionLevel, procedure::Procedure, - resource::AddFilters, update::ResourceTargetVariant, user::User, + update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ @@ -49,24 +48,7 @@ impl Resolve for State { ListProcedures { query }: ListProcedures, user: User, ) -> anyhow::Result { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Procedure::list_resources_for_user(filters, &user).await - } -} - -#[async_trait] -impl Resolve for State { - async fn resolve( - &self, - ListProceduresByIds { ids }: ListProceduresByIds, - user: User, - ) -> anyhow::Result { - Procedure::list_resources_for_user( - doc! { "_id": { "$in": ids } }, - &user, - ) - .await + Procedure::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index 354bf4c03..3970ec62d 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -7,12 +7,11 @@ use monitor_client::{ entities::{ permission::PermissionLevel, repo::{Repo, RepoActionState, RepoListItem}, - resource::AddFilters, update::ResourceTargetVariant, user::User, }, }; -use mungos::mongodb::bson::{doc, oid::ObjectId, Document}; +use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ @@ -46,9 +45,7 @@ impl Resolve for State { ListRepos { query }: ListRepos, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Repo::list_resources_for_user(filters, &user).await + Repo::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/api/read/search.rs b/bin/core/src/api/read/search.rs index ec941e81e..e89bfbc7d 100644 --- a/bin/core/src/api/read/search.rs +++ b/bin/core/src/api/read/search.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use monitor_client::{ api::read::{FindResources, FindResourcesResponse}, entities::{ - build, deployment, repo, server, + build, deployment, procedure, repo, server, update::ResourceTargetVariant::{self, *}, user::User, }, @@ -33,7 +33,7 @@ impl Resolve for State { for resource_type in resource_types { match resource_type { Server => { - res.servers = server::Server::list_resources_for_user( + res.servers = server::Server::query_resources_for_user( query.clone(), &user, ) @@ -41,216 +41,37 @@ impl Resolve for State { } Deployment => { res.deployments = - deployment::Deployment::list_resources_for_user( + deployment::Deployment::query_resources_for_user( query.clone(), &user, ) .await?; } Build => { - res.builds = build::Build::list_resources_for_user( + res.builds = build::Build::query_resources_for_user( query.clone(), &user, ) .await?; } Repo => { - res.repos = - repo::Repo::list_resources_for_user(query.clone(), &user) - .await?; + res.repos = repo::Repo::query_resources_for_user( + query.clone(), + &user, + ) + .await?; } - _ => unreachable!(), + Procedure => { + res.procedures = + procedure::Procedure::query_resources_for_user( + query.clone(), + &user, + ) + .await?; + } + _ => {} } } - - todo!() + Ok(res) } } - -// #[async_trait] -// impl Resolve for State { -// async fn resolve( -// &self, -// FindResources { search, tags }: FindResources, -// user: User, -// ) -> anyhow::Result { -// let SeperateTags { -// resource_types, -// server_ids, -// custom_tag_ids, -// } = seperate_tags(tags); - -// let mut query = doc! { -// "name": { "$regex": search } -// }; - -// if !user.admin { -// query.insert( -// format!("permissions.{}", user.id), -// doc! { "$in": ["read", "execute", "update"] }, -// ); -// } - -// if !custom_tag_ids.is_empty() { -// query.insert("tags", doc! { "$all": custom_tag_ids }); -// } - -// let mut response = FindResourcesResponse::default(); - -// for resource_type in resource_types { -// match resource_type { -// Server => { -// let servers = if server_ids.is_empty() { -// db_client().await.servers.get_some(query.clone(), None).await? -// } else { -// let server_ids = server_ids -// .iter() -// .map(|id| { -// ObjectId::from_str(id) -// .context("failed to parse server id as ObjectId") -// }) -// .collect::>>()?; -// let mut query = query.clone(); -// query.insert("_id", doc! { "$in": server_ids }); -// db_client().await.servers.get_some(query, None).await? -// }; -// for server in servers { -// let status = self -// .server_status_cache -// .get(&server.id) -// .await -// .map(|s| s.status) -// .unwrap_or_default(); -// let item = ServerListItem { -// status, -// id: server.id, -// name: server.name, -// tags: server.tags, -// }; -// response.servers.push(item); -// } -// } -// Deployment => { -// let mut query = query.clone(); - -// if !server_ids.is_empty() { -// query.insert("config.server_id", doc! { "$in": &server_ids }); -// } - -// let deployments = self -// .db -// .deployments -// .get_some(query, None) -// .await? -// .into_iter() -// .filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read); - -// for deployment in deployments { -// let status = self.deployment_status_cache.get(&deployment.id).await; -// let item = DeploymentListItem { -// id: deployment.id, -// name: deployment.name, -// tags: deployment.tags, -// state: status.as_ref().map(|s| s.state).unwrap_or_default(), -// status: status.as_ref().and_then(|s| { -// s.container.as_ref().and_then(|c| c.status.to_owned()) -// }), -// image: String::new(), -// server_id: String::new(), -// build_id: None, -// }; -// response.deployments.push(item); -// } -// } -// Build => { -// let mut query = query.clone(); - -// if !server_ids.is_empty() { -// query.insert( -// "config.builder.params.server_id", -// doc! { "$in": &server_ids }, -// ); -// } - -// let builds = self -// .db -// .builds -// .get_some(query, None) -// .await? -// .into_iter() -// .filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read); - -// for build in builds { -// let item = BuildListItem { -// id: build.id, -// name: build.name, -// tags: build.tags, -// last_built_at: build.last_built_at, -// version: build.config.version, -// }; -// response.builds.push(item); -// } -// } -// Repo => { -// let mut query = query.clone(); - -// if !server_ids.is_empty() { -// query.insert("config.server_id", doc! { "$in": &server_ids }); -// } - -// let repos = self -// .db -// .repos -// .get_some(query, None) -// .await? -// .into_iter() -// .filter(|d| d.get_user_permissions(&user.id) > PermissionLevel::Read); - -// for repo in repos { -// let item = RepoListItem { -// id: repo.id, -// name: repo.name, -// tags: repo.tags, -// last_pulled_at: repo.last_pulled_at, -// }; -// response.repos.push(item); -// } -// } -// _ => return Err(anyhow!("{resource_type} is not compatible with this route")), -// } -// } - -// Ok(response) -// } -// } - -// #[derive(Default)] -// struct SeperateTags { -// resource_types: Vec, -// server_ids: Vec, -// custom_tag_ids: Vec, -// } - -// fn seperate_tags(tags: Vec) -> SeperateTags { -// let mut seperated = SeperateTags::default(); - -// for tag in tags { -// match tag { -// Tag::Custom { tag_id } => seperated.custom_tag_ids.push(tag_id), -// Tag::Server { server_id } => seperated.server_ids.push(server_id), -// Tag::ResourceType { resource } => { -// if !matches!(resource, Builder | Alerter | System,) -// && !seperated.resource_types.contains(&resource) -// { -// seperated.resource_types.push(resource); -// } -// } -// } -// } - -// if seperated.resource_types.is_empty() { -// seperated.resource_types = FIND_RESOURCE_TYPES.to_vec(); -// } - -// seperated -// } diff --git a/bin/core/src/api/read/server.rs b/bin/core/src/api/read/server.rs index 480962892..77b152940 100644 --- a/bin/core/src/api/read/server.rs +++ b/bin/core/src/api/read/server.rs @@ -6,7 +6,6 @@ use monitor_client::{ entities::{ deployment::ContainerSummary, permission::PermissionLevel, - resource::AddFilters, server::{ docker_image::ImageSummary, docker_network::DockerNetwork, stats::SystemInformation, Server, ServerActionState, @@ -17,10 +16,7 @@ use monitor_client::{ }; use mungos::{ find::find_collect, - mongodb::{ - bson::{doc, Document}, - options::FindOptions, - }, + mongodb::{bson::doc, options::FindOptions}, }; use periphery_client::api::{self, GetAccountsResponse}; use resolver_api::{Resolve, ResolveToString}; @@ -42,7 +38,8 @@ impl Resolve for State { user: User, ) -> anyhow::Result { let servers = - Server::list_resources_for_user(Document::new(), &user).await?; + Server::list_resources_for_user(Default::default(), &user) + .await?; let mut res = GetServersSummaryResponse::default(); for server in servers { res.total += 1; @@ -107,9 +104,7 @@ impl Resolve for State { ListServers { query }: ListServers, user: User, ) -> anyhow::Result> { - let mut filters = Document::new(); - query.add_filters(&mut filters); - Server::list_resources_for_user(filters, &user).await + Server::list_resources_for_user(query, &user).await } } diff --git a/bin/core/src/helpers/resource.rs b/bin/core/src/helpers/resource.rs index d1a68ddb2..1f718e51d 100644 --- a/bin/core/src/helpers/resource.rs +++ b/bin/core/src/helpers/resource.rs @@ -7,27 +7,33 @@ use monitor_client::{ entities::{ alerter::{ Alerter, AlerterConfig, AlerterInfo, AlerterListItem, - AlerterListItemInfo, + AlerterListItemInfo, AlerterQuerySpecifics, }, build::{ - Build, BuildConfig, BuildInfo, BuildListItem, BuildListItemInfo, + Build, BuildConfig, BuildInfo, BuildListItem, + BuildListItemInfo, BuildQuerySpecifics, }, builder::{ Builder, BuilderConfig, BuilderListItem, BuilderListItemInfo, + BuilderQuerySpecifics, }, deployment::{ Deployment, DeploymentConfig, DeploymentImage, DeploymentListItem, DeploymentListItemInfo, + DeploymentQuerySpecifics, }, permission::PermissionLevel, procedure::{ Procedure, ProcedureConfig, ProcedureListItem, - ProcedureListItemInfo, + ProcedureListItemInfo, ProcedureQuerySpecifics, }, - repo::{Repo, RepoConfig, RepoInfo, RepoListItem}, - resource::Resource, + repo::{ + Repo, RepoConfig, RepoInfo, RepoListItem, RepoQuerySpecifics, + }, + resource::{AddFilters, Resource, ResourceQuery}, server::{ Server, ServerConfig, ServerListItem, ServerListItemInfo, + ServerQuerySpecifics, }, update::{ResourceTarget, ResourceTargetVariant}, user::User, @@ -65,6 +71,7 @@ pub trait StateResource { + Serialize + DeserializeOwned + 'static; + type QuerySpecifics: AddFilters + Default; fn name() -> &'static str; @@ -139,6 +146,16 @@ pub trait StateResource { } async fn list_resources_for_user( + mut query: ResourceQuery, + user: &User, + ) -> anyhow::Result> { + validate_resource_query_tags(&mut query).await; + let mut filters = Document::new(); + query.add_filters(&mut filters); + Self::query_resources_for_user(filters, user).await + } + + async fn query_resources_for_user( mut filters: Document, user: &User, ) -> anyhow::Result> { @@ -258,6 +275,7 @@ impl StateResource for Server { type ListItem = ServerListItem; type Config = ServerConfig; type Info = (); + type QuerySpecifics = ServerQuerySpecifics; fn name() -> &'static str { "server" @@ -302,6 +320,7 @@ impl StateResource for Deployment { type ListItem = DeploymentListItem; type Config = DeploymentConfig; type Info = (); + type QuerySpecifics = DeploymentQuerySpecifics; fn name() -> &'static str { "deployment" @@ -359,6 +378,7 @@ impl StateResource for Build { type ListItem = BuildListItem; type Config = BuildConfig; type Info = BuildInfo; + type QuerySpecifics = BuildQuerySpecifics; fn name() -> &'static str { "build" @@ -395,6 +415,7 @@ impl StateResource for Repo { type ListItem = RepoListItem; type Config = RepoConfig; type Info = RepoInfo; + type QuerySpecifics = RepoQuerySpecifics; fn name() -> &'static str { "repo" @@ -428,6 +449,7 @@ impl StateResource for Builder { type ListItem = BuilderListItem; type Config = BuilderConfig; type Info = (); + type QuerySpecifics = BuilderQuerySpecifics; fn name() -> &'static str { "builder" @@ -473,6 +495,7 @@ impl StateResource for Alerter { type ListItem = AlerterListItem; type Config = AlerterConfig; type Info = AlerterInfo; + type QuerySpecifics = AlerterQuerySpecifics; fn name() -> &'static str { "alerter" @@ -513,6 +536,7 @@ impl StateResource for Procedure { type ListItem = ProcedureListItem; type Config = ProcedureConfig; type Info = (); + type QuerySpecifics = ProcedureQuerySpecifics; fn name() -> &'static str { "procedure" @@ -605,3 +629,11 @@ pub async fn get_resource_ids_for_non_admin( .collect(); Ok(permissions) } + +pub async fn validate_resource_query_tags( + query: &mut ResourceQuery, +) { + let futures = query.tags.iter().map(|tag| get_tag(tag)); + let res = join_all(futures).await; + query.tags = res.into_iter().flatten().map(|tag| tag.id).collect(); +} diff --git a/bin/core/src/monitor/alert/mod.rs b/bin/core/src/monitor/alert/mod.rs index b73845f17..40d1479eb 100644 --- a/bin/core/src/monitor/alert/mod.rs +++ b/bin/core/src/monitor/alert/mod.rs @@ -5,10 +5,10 @@ use std::collections::HashMap; use anyhow::Context; use monitor_client::entities::{ + resource::ResourceQuery, server::{Server, ServerListItem}, user::User, }; -use mungos::mongodb::bson::Document; use crate::helpers::resource::StateResource; @@ -34,7 +34,7 @@ async fn get_all_servers_map() -> anyhow::Result<( HashMap, )> { let servers = Server::list_resources_for_user( - Document::new(), + ResourceQuery::default(), &User { admin: true, ..Default::default() diff --git a/client/core/rs/src/api/read/procedure.rs b/client/core/rs/src/api/read/procedure.rs index c2dc9a4c9..92ab6b20d 100644 --- a/client/core/rs/src/api/read/procedure.rs +++ b/client/core/rs/src/api/read/procedure.rs @@ -44,21 +44,6 @@ pub type ListProceduresResponse = Vec; // -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, -)] -#[empty_traits(MonitorReadRequest)] -#[response(ListProceduresByIdsResponse)] -pub struct ListProceduresByIds { - pub ids: Vec, -} - -#[typeshare] -pub type ListProceduresByIdsResponse = Vec; - -// - #[typeshare] #[derive( Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, diff --git a/client/core/rs/src/entities/resource.rs b/client/core/rs/src/entities/resource.rs index 0be70f5cc..f2e1be35b 100644 --- a/client/core/rs/src/entities/resource.rs +++ b/client/core/rs/src/entities/resource.rs @@ -1,7 +1,10 @@ +use std::str::FromStr; + use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use mungos::mongodb::bson::{ - doc, serde_helpers::hex_string_as_object_id, Document, + doc, oid::ObjectId, serde_helpers::hex_string_as_object_id, + Document, }; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -63,15 +66,29 @@ pub struct ResourceListItem { Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder, )] pub struct ResourceQuery { + #[serde(default)] + pub ids: Vec, #[serde(default)] pub names: Vec, - /// Pass Vec of tag ids + /// Pass Vec of tag ids or tag names #[serde(default)] pub tags: Vec, #[serde(default)] + pub tag_behavior: TagBehavior, + #[serde(default)] pub specific: T, } +#[typeshare] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +pub enum TagBehavior { + /// Returns resources which have strictly all the tags + #[default] + All, + /// Returns resources which have one or more of the tags + Any, +} + pub trait AddFilters { fn add_filters(&self, _filters: &mut Document) {} } @@ -80,11 +97,31 @@ impl AddFilters for () {} impl AddFilters for ResourceQuery { fn add_filters(&self, filters: &mut Document) { + if !self.ids.is_empty() { + let ids = self + .ids + .iter() + .flat_map(|id| ObjectId::from_str(id)) + .collect::>(); + filters.insert("_id", doc! { "$in": &ids }); + } if !self.names.is_empty() { filters.insert("name", doc! { "$in": &self.names }); } if !self.tags.is_empty() { - filters.insert("tags", doc! { "$all": &self.tags }); + match self.tag_behavior { + TagBehavior::All => { + filters.insert("tags", doc! { "$all": &self.tags }); + } + TagBehavior::Any => { + let ors = self + .tags + .iter() + .map(|tag| doc! { "tags": tag }) + .collect::>(); + filters.insert("$or", ors); + } + } } self.specific.add_filters(filters); } diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index 633b49ac2..053947116 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -23,7 +23,6 @@ export type ReadResponses = { GetProcedure: Types.GetProcedureResponse; GetProcedureActionState: Types.GetProcedureActionStateResponse; ListProcedures: Types.ListProceduresResponse; - ListProceduresByIds: Types.ListProceduresByIdsResponse; // ==== SERVER ==== GetServersSummary: Types.GetServersSummaryResponse; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 66b933e7f..36e28a17c 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -332,8 +332,6 @@ export type ProcedureListItem = ResourceListItem; export type ListProceduresResponse = ProcedureListItem[]; -export type ListProceduresByIdsResponse = ProcedureListItem[]; - export interface ProcedureActionState { running: boolean; } @@ -739,11 +737,20 @@ export type _PartialCustomAlerterConfig = Partial; export type _PartialSlackAlerterConfig = Partial; +export enum TagBehavior { + /** Returns resources which have strictly all the tags */ + All = "All", + /** Returns resources which have one or more of the tags */ + Any = "Any", +} + /** Passing empty Vec is the same as not filtering by that field */ export interface ResourceQuery { + ids?: string[]; names?: string[]; - /** Pass Vec of tag ids */ + /** Pass Vec of tag ids or tag names */ tags?: string[]; + tag_behavior?: TagBehavior; specific?: T; } @@ -1188,10 +1195,6 @@ export interface ListProcedures { query?: ProcedureQuery; } -export interface ListProceduresByIds { - ids: string[]; -} - export interface GetProceduresSummary { } @@ -1705,7 +1708,7 @@ export interface SlackAlerterConfig { } export interface ServerBuilderConfig { - id: string; + server_id: string; } export interface AwsBuilderConfig { @@ -1803,7 +1806,6 @@ export type ReadRequest = | { type: "GetProcedure", params: GetProcedure } | { type: "GetProcedureActionState", params: GetProcedureActionState } | { type: "ListProcedures", params: ListProcedures } - | { type: "ListProceduresByIds", params: ListProceduresByIds } | { type: "GetServersSummary", params: GetServersSummary } | { type: "GetServer", params: GetServer } | { type: "ListServers", params: ListServers } diff --git a/frontend/src/components/resources/deployment/index.tsx b/frontend/src/components/resources/deployment/index.tsx index 80ca22b77..ce1a5fa87 100644 --- a/frontend/src/components/resources/deployment/index.tsx +++ b/frontend/src/components/resources/deployment/index.tsx @@ -2,7 +2,7 @@ import { useRead, useWrite } from "@lib/hooks"; import { Types } from "@monitor/client"; import { RequiredResourceComponents } from "@types"; import { AlertTriangle, HardDrive, Rocket, Server } from "lucide-react"; -import { cn } from "@lib/utils"; +import { cn, snake_case_to_upper_space_case } from "@lib/utils"; import { useState } from "react"; import { NewResource, Section } from "@components/layouts"; @@ -22,6 +22,7 @@ import { DataTable } from "@ui/data-table"; import { ResourceComponents } from ".."; import { TagsWithBadge, useTagsFilter } from "@components/tags"; import { DeploymentsChart } from "@components/dashboard/deployments-chart"; +import { Button } from "@ui/button"; export const useDeployment = (id?: string) => useRead("ListDeployments", {}, { refetchInterval: 5000 }).data?.find( @@ -94,7 +95,6 @@ export const DeploymentTable = ({ } columns={[ { - accessorKey: "id", header: "Name", cell: ({ row }) => { const id = row.original.id; @@ -103,49 +103,71 @@ export const DeploymentTable = ({ to={`/deployments/${id}`} className="flex items-center gap-2" > - - + ); }, }, + { + header: "Image", + cell: ({ + row: { + original: { + info: { build_id, image }, + }, + }, + }) => { + const builds = useRead("ListBuilds", {}).data; + if (build_id) { + const build = builds?.find((build) => build.id === build_id); + if (build) { + return ( + + + + ); + } else { + return undefined; + } + } else { + const [img, _] = image.split(":"); + return img; + } + }, + }, { header: "Server", cell: ({ row }) => { const id = row.original.info.server_id; return ( - - - + + ); }, }, - // { - // header: "Build", - // cell: ({ row }) => { - // const id = row.original.info.build_id; - // if (!id) return null; - // return ( - // - // - // - // - // ); - // }, - // }, { - accessorKey: "info.image", - header: "Image", - }, - { - header: "Status", + header: "State", cell: ({ row }) => { - const status = row.original.info.status; - if (!status) return null; const state = row.original.info.state; const color = deployment_state_text_color(state); - return
{status}
; + return ( +
+ {snake_case_to_upper_space_case(state)} +
+ ); }, }, { diff --git a/frontend/src/components/tags/index.tsx b/frontend/src/components/tags/index.tsx index bc62c0066..4a2c050b8 100644 --- a/frontend/src/components/tags/index.tsx +++ b/frontend/src/components/tags/index.tsx @@ -12,13 +12,14 @@ import { import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; import { useToast } from "@ui/use-toast"; -import { atom, useAtom } from "jotai"; -import { PlusCircle, Tag } from "lucide-react"; +import { useAtom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; +import { MinusCircle, PlusCircle, Tag } from "lucide-react"; import { useEffect, useState } from "react"; type TargetExcludingSystem = Exclude; -const tagsAtom = atom([]); +const tagsAtom = atomWithStorage("tags-v0", []); export const useTagsFilter = () => { const [tags, _] = useAtom(tagsAtom); @@ -29,9 +30,8 @@ export const TagsFilter = () => { const [tags, setTags] = useAtom(tagsAtom); const all_tags = useRead("ListTags", {}).data; return ( -
- + setTags(tags.filter((id) => id !== tag_id))} /> @@ -61,6 +61,33 @@ export const TagsFilter = () => { ); }; +export const TagsFilterTags = ({ + tag_ids, + onBadgeClick, +}: { + tag_ids?: string[]; + onBadgeClick?: (tag_id: string) => void; +}) => { + const all_tags = useRead("ListTags", {}).data; + const get_name = (tag_id: string) => + all_tags?.find((t) => t._id?.$oid === tag_id)?.name ?? "unknown"; + return ( + <> + {tag_ids?.map((tag_id) => ( + onBadgeClick && onBadgeClick(tag_id)} + > + {get_name(tag_id)} + + + ))} + + ); +}; + export const ResourceTags = ({ target, click_to_delete, @@ -101,6 +128,8 @@ export const TagsWithBadge = ({ className?: string; }) => { const all_tags = useRead("ListTags", {}).data; + const get_name = (tag_id: string) => + all_tags?.find((t) => t._id?.$oid === tag_id)?.name ?? "unknown"; return ( <> {tag_ids?.map((tag_id) => ( @@ -110,7 +139,7 @@ export const TagsWithBadge = ({ className={className ?? "px-1.5 py-0.5 cursor-pointer"} onClick={() => onBadgeClick && onBadgeClick(tag_id)} > - {all_tags?.find((t) => t._id?.$oid === tag_id)?.name ?? "unknown"} + {get_name(tag_id)} ))} diff --git a/frontend/src/globals.css b/frontend/src/globals.css index 8a4581150..bf383d93e 100644 --- a/frontend/src/globals.css +++ b/frontend/src/globals.css @@ -43,14 +43,14 @@ --popover: 220.1 0.1% 98.2%; --popover-foreground: 220.1 40.1% 11.2%; - --primary: 220.1 0.1% 11.2%; + --primary: 220.1 20.1% 11.2%; --primary-foreground: 220.1 40.1% 98.2%; - --secondary: 220.1 40.1% 98.2%; - --secondary-foreground: 220.1 40.1% 11.2%; + --secondary: 220.1 40.1% 90.2%; + --secondary-foreground: 220.1 40.1% 5.2%; --muted: 220.1 40.1% 98.2%; - --muted-foreground: 215.4 20.1% 40.2%; + --muted-foreground: 220.1 20.1% 40.2%; --accent: 220.1 40.1% 98.2%; --accent-foreground: 220.1 40.1% 11.2%; diff --git a/frontend/src/lib/hooks.ts b/frontend/src/lib/hooks.ts index 4d0417d79..c136db6ec 100644 --- a/frontend/src/lib/hooks.ts +++ b/frontend/src/lib/hooks.ts @@ -130,4 +130,4 @@ export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => { }, [type, id, push]); return push; -}; +}; \ No newline at end of file diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 68ccd241e..c5b3d2f33 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -6,7 +6,6 @@ import { Link } from "react-router-dom"; import { ServerComponents } from "@components/resources/server"; import { AlertLevel } from "@components/util"; import { fmt_date_with_minutes } from "@lib/utils"; -import { useState } from "react"; import { Button } from "@ui/button"; import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { DeploymentComponents } from "@components/resources/deployment"; @@ -17,6 +16,8 @@ import { AlerterComponents } from "@components/resources/alerter"; import { ProcedureComponents } from "@components/resources/procedure/index"; import { TagsSummary } from "@components/dashboard/tags"; import { ApiKeysSummary } from "@components/dashboard/api-keys"; +import { atomWithStorage } from "jotai/utils"; +import { useAtom } from "jotai"; export const Dashboard = () => { return ( @@ -28,8 +29,10 @@ export const Dashboard = () => { ); }; +const openAtom = atomWithStorage("show-alerts-v0", true); + const OpenAlerts = () => { - const [open, setOpen] = useState(true); + const [open, setOpen] = useAtom(openAtom); const alerts = useRead("ListAlerts", { query: { resolved: false } }).data ?.alerts; if (!alerts || alerts.length === 0) return null; diff --git a/frontend/src/pages/resources.tsx b/frontend/src/pages/resources.tsx index 9f95ed517..0200b77f5 100644 --- a/frontend/src/pages/resources.tsx +++ b/frontend/src/pages/resources.tsx @@ -5,6 +5,10 @@ import { useRead, useResourceParamType } from "@lib/hooks"; import { Button } from "@ui/button"; import { Input } from "@ui/input"; import { useState } from "react"; +import { atomWithStorage } from "jotai/utils"; +import { useAtom } from "jotai"; + +const viewAtom = atomWithStorage<"cards" | "table">("list-show-as-v0", "table"); export const Resources = () => { const type = useResourceParamType(); @@ -15,7 +19,7 @@ export const Resources = () => { const list = useRead(`List${type}s`, { query: { tags } }).data; const [search, set] = useState(""); - const [view, setView] = useState<"cards" | "table">("table"); + const [view, setView] = useAtom(viewAtom); return (