mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-28 11:49:39 -05:00
resource query tags match on name or id, All or Any mode
This commit is contained in:
@@ -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<ListAlerters, User> for State {
|
||||
ListAlerters { query }: ListAlerters,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<AlerterListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ListBuilds, User> for State {
|
||||
ListBuilds { query }: ListBuilds,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<BuildListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ListBuilders, User> for State {
|
||||
ListBuilders { query }: ListBuilders,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<BuilderListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ListDeployments, User> for State {
|
||||
ListDeployments { query }: ListDeployments,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<DeploymentListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ enum ReadRequest {
|
||||
GetProcedure(GetProcedure),
|
||||
GetProcedureActionState(GetProcedureActionState),
|
||||
ListProcedures(ListProcedures),
|
||||
ListProceduresByIds(ListProceduresByIds),
|
||||
|
||||
// ==== SERVER ====
|
||||
GetServersSummary(GetServersSummary),
|
||||
|
||||
@@ -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<ListProcedures, User> for State {
|
||||
ListProcedures { query }: ListProcedures,
|
||||
user: User,
|
||||
) -> anyhow::Result<ListProceduresResponse> {
|
||||
let mut filters = Document::new();
|
||||
query.add_filters(&mut filters);
|
||||
Procedure::list_resources_for_user(filters, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Resolve<ListProceduresByIds, User> for State {
|
||||
async fn resolve(
|
||||
&self,
|
||||
ListProceduresByIds { ids }: ListProceduresByIds,
|
||||
user: User,
|
||||
) -> anyhow::Result<ListProceduresByIdsResponse> {
|
||||
Procedure::list_resources_for_user(
|
||||
doc! { "_id": { "$in": ids } },
|
||||
&user,
|
||||
)
|
||||
.await
|
||||
Procedure::list_resources_for_user(query, &user).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ListRepos, User> for State {
|
||||
ListRepos { query }: ListRepos,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<RepoListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FindResources, User> 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<FindResources, User> 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<FindResources, User> for State {
|
||||
// async fn resolve(
|
||||
// &self,
|
||||
// FindResources { search, tags }: FindResources,
|
||||
// user: User,
|
||||
// ) -> anyhow::Result<FindResourcesResponse> {
|
||||
// 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::<anyhow::Result<Vec<_>>>()?;
|
||||
// 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<ResourceTargetVariant>,
|
||||
// server_ids: Vec<String>,
|
||||
// custom_tag_ids: Vec<String>,
|
||||
// }
|
||||
|
||||
// fn seperate_tags(tags: Vec<Tag>) -> 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
|
||||
// }
|
||||
|
||||
@@ -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<GetServersSummary, User> for State {
|
||||
user: User,
|
||||
) -> anyhow::Result<GetServersSummaryResponse> {
|
||||
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<ListServers, User> for State {
|
||||
ListServers { query }: ListServers,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<ServerListItem>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Self::QuerySpecifics>,
|
||||
user: &User,
|
||||
) -> anyhow::Result<Vec<Self::ListItem>> {
|
||||
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<Vec<Self::ListItem>> {
|
||||
@@ -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<T: Default>(
|
||||
query: &mut ResourceQuery<T>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<String, String>,
|
||||
)> {
|
||||
let servers = Server::list_resources_for_user(
|
||||
Document::new(),
|
||||
ResourceQuery::default(),
|
||||
&User {
|
||||
admin: true,
|
||||
..Default::default()
|
||||
|
||||
@@ -44,21 +44,6 @@ pub type ListProceduresResponse = Vec<ProcedureListItem>;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorReadRequest)]
|
||||
#[response(ListProceduresByIdsResponse)]
|
||||
pub struct ListProceduresByIds {
|
||||
pub ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type ListProceduresByIdsResponse = Vec<ProcedureListItem>;
|
||||
|
||||
//
|
||||
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
|
||||
@@ -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<Info> {
|
||||
Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder,
|
||||
)]
|
||||
pub struct ResourceQuery<T: Default> {
|
||||
#[serde(default)]
|
||||
pub ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub names: Vec<String>,
|
||||
/// Pass Vec of tag ids
|
||||
/// Pass Vec of tag ids or tag names
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
#[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<T: AddFilters + Default> AddFilters for ResourceQuery<T> {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
filters.insert("$or", ors);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.specific.add_filters(filters);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export type ReadResponses = {
|
||||
GetProcedure: Types.GetProcedureResponse;
|
||||
GetProcedureActionState: Types.GetProcedureActionStateResponse;
|
||||
ListProcedures: Types.ListProceduresResponse;
|
||||
ListProceduresByIds: Types.ListProceduresByIdsResponse;
|
||||
|
||||
// ==== SERVER ====
|
||||
GetServersSummary: Types.GetServersSummaryResponse;
|
||||
|
||||
@@ -332,8 +332,6 @@ export type ProcedureListItem = ResourceListItem<ProcedureListItemInfo>;
|
||||
|
||||
export type ListProceduresResponse = ProcedureListItem[];
|
||||
|
||||
export type ListProceduresByIdsResponse = ProcedureListItem[];
|
||||
|
||||
export interface ProcedureActionState {
|
||||
running: boolean;
|
||||
}
|
||||
@@ -739,11 +737,20 @@ export type _PartialCustomAlerterConfig = Partial<CustomAlerterConfig>;
|
||||
|
||||
export type _PartialSlackAlerterConfig = Partial<SlackAlerterConfig>;
|
||||
|
||||
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<T> {
|
||||
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 }
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<ResourceComponents.Deployment.Icon id={id} />
|
||||
<ResourceComponents.Deployment.Name id={id} />
|
||||
<Button variant="link" className="flex gap-2 items-center p-0">
|
||||
<ResourceComponents.Deployment.Icon id={id} />
|
||||
<ResourceComponents.Deployment.Name id={id} />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<Link to={`/builds/${build_id}`}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="flex gap-2 items-center p-0"
|
||||
>
|
||||
<ResourceComponents.Build.Icon id={build_id} />
|
||||
<ResourceComponents.Build.Name id={build_id} />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
const [img, _] = image.split(":");
|
||||
return img;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Server",
|
||||
cell: ({ row }) => {
|
||||
const id = row.original.info.server_id;
|
||||
return (
|
||||
<Link to={`/servers/${id}`} className="flex items-center gap-2">
|
||||
<ResourceComponents.Server.Icon id={id} />
|
||||
<ResourceComponents.Server.Name id={id} />
|
||||
<Link to={`/servers/${id}`}>
|
||||
<Button variant="link" className="flex items-center gap-2 p-0">
|
||||
<ResourceComponents.Server.Icon id={id} />
|
||||
<ResourceComponents.Server.Name id={id} />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// header: "Build",
|
||||
// cell: ({ row }) => {
|
||||
// const id = row.original.info.build_id;
|
||||
// if (!id) return null;
|
||||
// return (
|
||||
// <Link to={`/builds/${id}`} className="flex items-center gap-2">
|
||||
// <ResourceComponents.Build.Icon id={id} />
|
||||
// <ResourceComponents.Build.Name id={id} />
|
||||
// </Link>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
{
|
||||
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 <div className={color}>{status}</div>;
|
||||
return (
|
||||
<div className={color}>
|
||||
{snake_case_to_upper_space_case(state)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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<Types.ResourceTarget, { type: "System" }>;
|
||||
|
||||
const tagsAtom = atom<string[]>([]);
|
||||
const tagsAtom = atomWithStorage<string[]>("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 (
|
||||
<div className="flex gap-4">
|
||||
<TagsWithBadge
|
||||
className="cursor-pointer"
|
||||
<div className="flex gap-4 items-center">
|
||||
<TagsFilterTags
|
||||
tag_ids={tags}
|
||||
onBadgeClick={(tag_id) => 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) => (
|
||||
<Badge
|
||||
key={tag_id}
|
||||
variant="destructive"
|
||||
className="flex gap-1 px-2 py-1.5 cursor-pointer"
|
||||
onClick={() => onBadgeClick && onBadgeClick(tag_id)}
|
||||
>
|
||||
{get_name(tag_id)}
|
||||
<MinusCircle className="w-3 h-3" />
|
||||
</Badge>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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)}
|
||||
</Badge>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -130,4 +130,4 @@ export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => {
|
||||
}, [type, id, push]);
|
||||
|
||||
return push;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<Page
|
||||
|
||||
Reference in New Issue
Block a user