Compare commits

...

2 Commits

Author SHA1 Message Date
Maxwell Becker
cb270f4dff 1.15.12 (#139)
* add containers link to mobile dropdown

* fix update / alert not showing permission issue

* prevent disk alert back and forth

* improve user group pending toml
2024-10-18 17:14:22 -07:00
Matt Foxx
21666cf9b3 feat: Add docs link to topbar (#134) 2024-10-18 16:10:01 -07:00
13 changed files with 268 additions and 309 deletions

24
Cargo.lock generated
View File

@@ -41,7 +41,7 @@ dependencies = [
[[package]]
name = "alerter"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"axum",
@@ -943,7 +943,7 @@ dependencies = [
[[package]]
name = "command"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"komodo_client",
"run_command",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]]
name = "environment_file"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"thiserror",
]
@@ -1439,7 +1439,7 @@ dependencies = [
[[package]]
name = "formatting"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"serror",
]
@@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "git"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"command",
@@ -2192,7 +2192,7 @@ dependencies = [
[[package]]
name = "komodo_cli"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"clap",
@@ -2208,7 +2208,7 @@ dependencies = [
[[package]]
name = "komodo_client"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2239,7 +2239,7 @@ dependencies = [
[[package]]
name = "komodo_core"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2296,7 +2296,7 @@ dependencies = [
[[package]]
name = "komodo_periphery"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"async_timing_util",
@@ -2383,7 +2383,7 @@ dependencies = [
[[package]]
name = "logger"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"komodo_client",
@@ -3089,7 +3089,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "periphery_client"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"komodo_client",
@@ -4867,7 +4867,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "update_logger"
version = "1.15.10"
version = "1.15.12"
dependencies = [
"anyhow",
"komodo_client",

View File

@@ -9,7 +9,7 @@ members = [
]
[workspace.package]
version = "1.15.11"
version = "1.15.12"
edition = "2021"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"

View File

@@ -3,7 +3,10 @@ use komodo_client::{
api::read::{
GetAlert, GetAlertResponse, ListAlerts, ListAlertsResponse,
},
entities::{deployment::Deployment, server::Server, user::User},
entities::{
deployment::Deployment, server::Server, stack::Stack,
sync::ResourceSync, user::User,
},
};
use mungos::{
by_id::find_one_by_id,
@@ -30,12 +33,18 @@ impl Resolve<ListAlerts, User> for State {
if !user.admin && !core_config().transparent_mode {
let server_ids =
get_resource_ids_for_user::<Server>(&user).await?;
let stack_ids =
get_resource_ids_for_user::<Stack>(&user).await?;
let deployment_ids =
get_resource_ids_for_user::<Deployment>(&user).await?;
let sync_ids =
get_resource_ids_for_user::<ResourceSync>(&user).await?;
query.extend(doc! {
"$or": [
{ "target.type": "Server", "target.id": { "$in": &server_ids } },
{ "target.type": "Stack", "target.id": { "$in": &stack_ids } },
{ "target.type": "Deployment", "target.id": { "$in": &deployment_ids } },
{ "target.type": "ResourceSync", "target.id": { "$in": &sync_ids } },
]
});
}

View File

@@ -70,7 +70,7 @@ impl Resolve<GetAlertersSummary, User> for State {
user: User,
) -> anyhow::Result<GetAlertersSummaryResponse> {
let query =
match resource::get_resource_ids_for_user::<Alerter>(&user)
match resource::get_resource_object_ids_for_user::<Alerter>(&user)
.await?
{
Some(ids) => doc! {

View File

@@ -70,7 +70,7 @@ impl Resolve<GetBuildersSummary, User> for State {
user: User,
) -> anyhow::Result<GetBuildersSummaryResponse> {
let query =
match resource::get_resource_ids_for_user::<Builder>(&user)
match resource::get_resource_object_ids_for_user::<Builder>(&user)
.await?
{
Some(ids) => doc! {

View File

@@ -71,7 +71,7 @@ impl Resolve<GetServerTemplatesSummary, User> for State {
GetServerTemplatesSummary {}: GetServerTemplatesSummary,
user: User,
) -> anyhow::Result<GetServerTemplatesSummaryResponse> {
let query = match resource::get_resource_ids_for_user::<
let query = match resource::get_resource_object_ids_for_user::<
ServerTemplate,
>(&user)
.await?

View File

@@ -1,27 +1,16 @@
use std::collections::HashMap;
use anyhow::Context;
use komodo_client::{
api::read::{
ExportAllResourcesToToml, ExportAllResourcesToTomlResponse,
ExportResourcesToToml, ExportResourcesToTomlResponse,
GetUserGroup, ListUserTargetPermissions,
ListUserGroups,
},
entities::{
alerter::Alerter,
build::Build,
builder::Builder,
deployment::Deployment,
permission::{PermissionLevel, UserTarget},
procedure::Procedure,
repo::Repo,
resource::ResourceQuery,
server::Server,
server_template::ServerTemplate,
stack::Stack,
sync::ResourceSync,
toml::{PermissionToml, ResourcesToml, UserGroupToml},
user::User,
alerter::Alerter, build::Build, builder::Builder,
deployment::Deployment, permission::PermissionLevel,
procedure::Procedure, repo::Repo, resource::ResourceQuery,
server::Server, server_template::ServerTemplate, stack::Stack,
sync::ResourceSync, toml::ResourcesToml, user::User,
ResourceTarget,
},
};
@@ -36,6 +25,7 @@ use crate::{
state::{db_client, State},
sync::{
toml::{convert_resource, ToToml, TOML_PRETTY_OPTIONS},
user_groups::convert_user_groups,
AllResourcesById,
},
};
@@ -385,122 +375,17 @@ async fn add_user_groups(
all: &AllResourcesById,
user: &User,
) -> anyhow::Result<()> {
let db = db_client();
let usernames = find_collect(&db.users, None, None)
let user_groups = State
.resolve(ListUserGroups {}, user.clone())
.await?
.into_iter()
.map(|user| (user.id, user.username))
.collect::<HashMap<_, _>>();
for user_group in user_groups {
let ug = State
.resolve(GetUserGroup { user_group }, user.clone())
.await?;
// this method is admin only, but we already know user can see user group if above does not return Err
let permissions = State
.resolve(
ListUserTargetPermissions {
user_target: UserTarget::UserGroup(ug.id),
},
User {
admin: true,
..Default::default()
},
)
.await?
.into_iter()
.map(|mut permission| {
match &mut permission.resource_target {
ResourceTarget::Build(id) => {
*id = all
.builds
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Builder(id) => {
*id = all
.builders
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Deployment(id) => {
*id = all
.deployments
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Server(id) => {
*id = all
.servers
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
*id = all
.repos
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
*id = all
.alerters
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Procedure(id) => {
*id = all
.procedures
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::ServerTemplate(id) => {
*id = all
.templates
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::ResourceSync(id) => {
*id = all
.syncs
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Stack(id) => {
*id = all
.stacks
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::System(_) => {}
}
PermissionToml {
target: permission.resource_target,
level: permission.level,
}
})
.collect();
res.user_groups.push(UserGroupToml {
name: ug.name,
users: ug
.users
.into_iter()
.filter_map(|user_id| usernames.get(&user_id).cloned())
.collect(),
all: ug.all,
permissions,
.filter(|ug| {
user_groups.contains(&ug.name) || user_groups.contains(&ug.id)
});
}
let mut ug = Vec::with_capacity(user_groups.size_hint().0);
convert_user_groups(user_groups, all, &mut ug).await?;
res.user_groups = ug.into_iter().map(|ug| ug.1).collect();
Ok(())
}

View File

@@ -104,6 +104,16 @@ impl Resolve<ListUpdates, User> for State {
})
.unwrap_or_else(|| doc! { "target.type": "Procedure" });
// let action_query =
// resource::get_resource_ids_for_user::<Action>(&user)
// .await?
// .map(|ids| {
// doc! {
// "target.type": "Action", "target.id": { "$in": ids }
// }
// })
// .unwrap_or_else(|| doc! { "target.type": "Action" });
let builder_query =
resource::get_resource_ids_for_user::<Builder>(&user)
.await?
@@ -124,27 +134,27 @@ impl Resolve<ListUpdates, User> for State {
})
.unwrap_or_else(|| doc! { "target.type": "Alerter" });
let server_template_query = resource::get_resource_ids_for_user::<ServerTemplate>(
&user,
)
.await?
.map(|ids| {
doc! {
"target.type": "ServerTemplate", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ServerTemplate" });
let server_template_query =
resource::get_resource_ids_for_user::<ServerTemplate>(&user)
.await?
.map(|ids| {
doc! {
"target.type": "ServerTemplate", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ServerTemplate" });
let resource_sync_query = resource::get_resource_ids_for_user::<ResourceSync>(
&user,
)
.await?
.map(|ids| {
doc! {
"target.type": "ResourceSync", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
let resource_sync_query =
resource::get_resource_ids_for_user::<ResourceSync>(
&user,
)
.await?
.map(|ids| {
doc! {
"target.type": "ResourceSync", "target.id": { "$in": ids }
}
})
.unwrap_or_else(|| doc! { "target.type": "ResourceSync" });
let mut query = query.unwrap_or_default();
query.extend(doc! {
@@ -155,6 +165,7 @@ impl Resolve<ListUpdates, User> for State {
build_query,
repo_query,
procedure_query,
// action_query,
alerter_query,
builder_query,
server_template_query,

View File

@@ -584,8 +584,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
let variable_updates = if sync.config.match_tags.is_empty() {
crate::sync::variables::get_updates_for_view(
&resources.variables,
// Delete doesn't work with variables when match tags are set
sync.config.match_tags.is_empty() && delete,
delete,
)
.await?
} else {
@@ -595,8 +594,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
let user_group_updates = if sync.config.match_tags.is_empty() {
crate::sync::user_groups::get_updates_for_view(
resources.user_groups,
// Delete doesn't work with user groups when match tags are set
sync.config.match_tags.is_empty() && delete,
delete,
&all_resources,
)
.await?

View File

@@ -316,8 +316,8 @@ pub async fn alert_servers(
Some(mut alert),
_,
) => {
// Disk is persistent, update alert if health changes regardless of direction
if health.level != alert.level {
// modify alert level only if it has increased
if health.level < alert.level {
let disk =
server_status.stats.as_ref().and_then(|stats| {
stats.disks.iter().find(|disk| disk.mount == *path)

View File

@@ -240,9 +240,24 @@ pub async fn get_check_permissions<T: KomodoResource>(
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
#[instrument(level = "debug")]
pub async fn get_resource_ids_for_user<T: KomodoResource>(
pub async fn get_resource_object_ids_for_user<T: KomodoResource>(
user: &User,
) -> anyhow::Result<Option<Vec<ObjectId>>> {
get_resource_ids_for_user::<T>(user).await.map(|ids| {
ids.map(|ids| {
ids
.into_iter()
.flat_map(|id| ObjectId::from_str(&id))
.collect()
})
})
}
/// Returns None if still no need to filter by resource id (eg transparent mode, group membership with all access).
#[instrument(level = "debug")]
pub async fn get_resource_ids_for_user<T: KomodoResource>(
user: &User,
) -> anyhow::Result<Option<Vec<String>>> {
// Check admin or transparent mode
if user.admin || core_config().transparent_mode {
return Ok(None);
@@ -271,7 +286,7 @@ pub async fn get_resource_ids_for_user<T: KomodoResource>(
// Get any resources with non-none base permission,
find_collect(
T::coll().await,
doc! { "base_permission": { "$ne": "None" } },
doc! { "base_permission": { "$exists": true, "$ne": "None" } },
None,
)
.map(|res| res.with_context(|| format!(
@@ -283,7 +298,7 @@ pub async fn get_resource_ids_for_user<T: KomodoResource>(
doc! {
"$or": user_target_query(&user.id, &groups)?,
"resource_target.type": resource_type.as_ref(),
"level": { "$in": ["Read", "Execute", "Write"] }
"level": { "$exists": true, "$ne": "None" }
},
None,
)
@@ -297,9 +312,6 @@ pub async fn get_resource_ids_for_user<T: KomodoResource>(
// Chain in the ones with non-None base permissions
.chain(base.into_iter().map(|res| res.id))
// collect into hashset first to remove any duplicates
.collect::<HashSet<_>>()
.into_iter()
.flat_map(|id| ObjectId::from_str(&id))
.collect::<HashSet<_>>();
Ok(Some(ids.into_iter().collect()))
@@ -418,7 +430,9 @@ pub async fn list_full_for_user_using_document<T: KomodoResource>(
mut filters: Document,
user: &User,
) -> anyhow::Result<Vec<Resource<T::Config, T::Info>>> {
if let Some(ids) = get_resource_ids_for_user::<T>(user).await? {
if let Some(ids) =
get_resource_object_ids_for_user::<T>(user).await?
{
filters.insert("_id", doc! { "$in": ids });
}
find_collect(

View File

@@ -15,7 +15,8 @@ use komodo_client::{
sync::DiffData,
toml::{PermissionToml, UserGroupToml},
update::Log,
user::sync_user,
user::{sync_user, User},
user_group::UserGroup,
ResourceTarget, ResourceTargetVariant,
},
};
@@ -43,17 +44,21 @@ pub async fn get_updates_for_view(
delete: bool,
all_resources: &AllResourcesById,
) -> anyhow::Result<Vec<DiffData>> {
let map = find_collect(&db_client().user_groups, None, None)
let _curr = find_collect(&db_client().user_groups, None, None)
.await
.context("failed to query db for UserGroups")?
.context("failed to query db for UserGroups")?;
let mut curr = Vec::with_capacity(_curr.capacity());
convert_user_groups(_curr.into_iter(), all_resources, &mut curr)
.await?;
let map = curr
.into_iter()
.map(|ug| (ug.name.clone(), ug))
.map(|ug| (ug.1.name.clone(), ug))
.collect::<HashMap<_, _>>();
let mut diffs = Vec::<DiffData>::new();
if delete {
for user_group in map.values() {
for (_id, user_group) in map.values() {
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
diffs.push(DiffData::Delete {
current: format!(
@@ -66,13 +71,6 @@ pub async fn get_updates_for_view(
}
}
let id_to_user = find_collect(&db_client().users, None, None)
.await
.context("failed to query db for Users")?
.into_iter()
.map(|user| (user.id.clone(), user))
.collect::<HashMap<_, _>>();
for mut user_group in user_groups {
user_group
.permissions
@@ -90,7 +88,10 @@ pub async fn get_updates_for_view(
)
})?;
let original = match map.get(&user_group.name).cloned() {
let (_original_id, original) = match map
.get(&user_group.name)
.cloned()
{
Some(original) => original,
None => {
diffs.push(DiffData::Create {
@@ -104,121 +105,16 @@ pub async fn get_updates_for_view(
continue;
}
};
let mut original_users = original
.users
.clone()
.into_iter()
.filter_map(|user_id| {
id_to_user.get(&user_id).map(|u| u.username.clone())
})
.collect::<Vec<_>>();
let mut original_permissions = State
.resolve(
ListUserTargetPermissions {
user_target: UserTarget::UserGroup(original.id.clone()),
},
sync_user().to_owned(),
)
.await
.context("failed to query for existing UserGroup permissions")?
.into_iter()
.filter(|p| p.level > PermissionLevel::None)
.map(|mut p| {
// replace the ids with names
match &mut p.resource_target {
ResourceTarget::System(_) => {}
ResourceTarget::Build(id) => {
*id = all_resources
.builds
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Builder(id) => {
*id = all_resources
.builders
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Deployment(id) => {
*id = all_resources
.deployments
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Server(id) => {
*id = all_resources
.servers
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
*id = all_resources
.repos
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
*id = all_resources
.alerters
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Procedure(id) => {
*id = all_resources
.procedures
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::ServerTemplate(id) => {
*id = all_resources
.templates
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::ResourceSync(id) => {
*id = all_resources
.syncs
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
ResourceTarget::Stack(id) => {
*id = all_resources
.stacks
.get(id)
.map(|b| b.name.clone())
.unwrap_or_default()
}
}
PermissionToml {
target: p.resource_target,
level: p.level,
}
})
.collect::<Vec<_>>();
original_users.sort();
user_group.users.sort();
let all_diff = diff_group_all(&original.all, &user_group.all);
user_group.permissions.sort_by(sort_permissions);
original_permissions.sort_by(sort_permissions);
let update_users = user_group.users != original_users;
let update_users = user_group.users != original.users;
let update_all = !all_diff.is_empty();
let update_permissions =
user_group.permissions != original_permissions;
user_group.permissions != original.permissions;
// only add log after diff detected
if update_users || update_all || update_permissions {
@@ -900,3 +796,132 @@ fn diff_group_all(
to_update
}
pub async fn convert_user_groups(
user_groups: impl Iterator<Item = UserGroup>,
all: &AllResourcesById,
res: &mut Vec<(String, UserGroupToml)>,
) -> anyhow::Result<()> {
let db = db_client();
let usernames = find_collect(&db.users, None, None)
.await?
.into_iter()
.map(|user| (user.id, user.username))
.collect::<HashMap<_, _>>();
for user_group in user_groups {
// this method is admin only, but we already know user can see user group if above does not return Err
let mut permissions = State
.resolve(
ListUserTargetPermissions {
user_target: UserTarget::UserGroup(user_group.id.clone()),
},
User {
admin: true,
..Default::default()
},
)
.await?
.into_iter()
.map(|mut permission| {
match &mut permission.resource_target {
ResourceTarget::Build(id) => {
*id = all
.builds
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Builder(id) => {
*id = all
.builders
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Deployment(id) => {
*id = all
.deployments
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Server(id) => {
*id = all
.servers
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Repo(id) => {
*id = all
.repos
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Alerter(id) => {
*id = all
.alerters
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Procedure(id) => {
*id = all
.procedures
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::ServerTemplate(id) => {
*id = all
.templates
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::ResourceSync(id) => {
*id = all
.syncs
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::Stack(id) => {
*id = all
.stacks
.get(id)
.map(|r| r.name.clone())
.unwrap_or_default()
}
ResourceTarget::System(_) => {}
}
PermissionToml {
target: permission.resource_target,
level: permission.level,
}
})
.collect::<Vec<_>>();
let mut users = user_group
.users
.into_iter()
.filter_map(|user_id| usernames.get(&user_id).cloned())
.collect::<Vec<_>>();
permissions.sort_by(sort_permissions);
users.sort();
res.push((
user_group.id,
UserGroupToml {
name: user_group.name,
users,
all: user_group.all,
permissions,
},
));
}
Ok(())
}

View File

@@ -8,6 +8,7 @@ import {
FileQuestion,
FolderTree,
Keyboard,
LayoutDashboard,
Settings,
User,
Users,
@@ -66,7 +67,10 @@ export const Topbar = () => {
<div className="flex justify-end items-center gap-2">
<MobileDropdown />
<OmniSearch setOpen={setOmniOpen} className="lg:hidden" />
<Version />
<div className="flex gap-0">
<Docs />
<Version />
</div>
<WsStatusIndicator />
<KeyboardShortcuts />
<TopbarAlerts />
@@ -80,6 +84,18 @@ export const Topbar = () => {
);
};
const Docs = () => (
<a
href="https://komo.do/docs/intro"
target="_blank"
className="hidden lg:block"
>
<Button variant="link" size="sm">
<div>Docs</div>
</Button>
</a>
);
const Version = () => {
const version = useRead("GetVersion", {}).data?.version;
@@ -112,11 +128,13 @@ const MobileDropdown = () => {
: type) + "s",
]
: location.pathname === "/" && view === "Dashboard"
? [<Box className="w-4 h-4" />, "Dashboard"]
? [<LayoutDashboard className="w-4 h-4" />, "Dashboard"]
: location.pathname === "/" && view === "Resources"
? [<Boxes className="w-4 h-4" />, "Resources"]
: location.pathname === "/" && view === "Tree"
? [<FolderTree className="w-4 h-4" />, "Tree"]
: location.pathname === "/containers"
? [<Box className="w-4 h-4" />, "Containers"]
: location.pathname === "/settings"
? [<Settings className="w-4 h-4" />, "Settings"]
: location.pathname === "/alerts"
@@ -144,7 +162,7 @@ const MobileDropdown = () => {
<DropdownMenuGroup>
<DropdownLinkItem
label="Dashboard"
icon={<Box className="w-4 h-4" />}
icon={<LayoutDashboard className="w-4 h-4" />}
to="/"
onClick={() => setView("Dashboard")}
/>
@@ -154,12 +172,11 @@ const MobileDropdown = () => {
to="/"
onClick={() => setView("Resources")}
/>
{/* <DropdownLinkItem
label="Tree"
icon={<FolderTree className="w-4 h-4" />}
to="/"
onClick={() => setView("Tree")}
/> */}
<DropdownLinkItem
label="Containers"
icon={<Box className="w-4 h-4" />}
to="/containers"
/>
<DropdownMenuSeparator />