forked from github-starred/komodo
* feat: add maintenance window management to suppress alerts during planned activities (#550) * feat: add scheduled maintenance windows to server configuration - Add maintenance window configuration to server entities - Implement maintenance window UI components with data table layout - Add maintenance tab to server interface - Suppress alerts during maintenance windows * chore: enhance maintenance windows with types and permission improvements - Add chrono dependency to Rust client core for time handling - Add comprehensive TypeScript types for maintenance windows (MaintenanceWindow, MaintenanceScheduleType, MaintenanceTime, DayOfWeek) - Improve maintenance config component to use usePermissions hook for better permission handling - Update package dependencies * feat: restore alert buffer system to prevent noise * fix yarn fe * fix the merge with new alerting changes * move alert buffer handle out of loop * nit * fix server version changes * unneeded buffer clear --------- Co-authored-by: mbecker20 <becker.maxh@gmail.com> * set version 1.18.2 * failed OIDC provider init doesn't cause panic, just error log * OIDC: use userinfo endpoint to get preffered username for user. * add profile to scopes and account for username already taken * search through server docker lists * move maintenance stuff * refactor maintenance schedules to have more toml compatible structure * daily schedule type use struct * add timezone to core info response * frontend can build with new maintenance types * Action monaco expose KomodoClient to init another client * flatten out the nested enum * update maintenance schedule types * dev-3 * implement maintenance windows on alerters * dev-4 * add IanaTimezone enum * typeshare timezone enum * maintenance modes almost done on servers AND alerters * maintenance schedules working * remove mention of migrator * Procedure / Action schedule timezone selector * improve timezone selector to display configure core TZ * dev-5 * refetch core version * add version to server list item info * add periphery version in server table * dev-6 * capitalize Unknown server status in cache * handle unknown version case * set server table sizes * default resource_poll_interval 1-hr * ensure parent folder exists before cloning * document Build Attach permission * git actions return absolute path * stack linked repos * resource toml replace linked_repo id with name * validate incoming linked repo * add linked repo to stack list item info * stack list item info resolved linked repo information * configure linked repo stack * to repo links * dev-7 * sync: replace linked repo with name for execute compare * obscure provider tokens in table view * clean up stack write w/ refactor * Resource Sync / Build start support Repo attach * add stack clone path config * Builds + syncs can link to repos * dev-9 * update ts * fix linked repo not included in resource sync list item info * add linked repo UI for builds / syncs * fix commit linked repo sync * include linked repo syncs * correct Sync / Build config mode * dev-12 fix resource sync inclusion w/ linked_repo * remove unneed sync commit todo!() * fix other config.repo.is_empty issues * replace ids in all to toml exports * Ensure git pull before commit for linear history, add to update logs * fix fe for linked repo cases * consolidate linked repo config component * fix resource sync commit behavior * dev 17 * Build uses Pull or Clone api to setup build source * capitalize Clone Repo stage * mount PullOrCloneRepo * dev-19 * Expand supported container names and also avoid unnecessary name formatting * dev-20 * add periphery /terminal/execute/container api * periphery client execute_container_exec method * implement execute container, deployment, stack exec * gen types * execute container exec method * clean up client / fix fe * enumerate exec ts methods for each resource type * fix and gen ts client * fix FE use connect_exec * add url log when terminal ws fail to connect * ts client server allow terminal.js * FE preload terminal.js / .d.ts * dev-23 fix stack terminal fail to connect when not explicitly setting container name * update docs on attach perms * 1.18.2 --------- Co-authored-by: Samuel Cardoso <R3D2@users.noreply.github.com>
517 lines
13 KiB
Rust
517 lines
13 KiB
Rust
use anyhow::Context;
|
|
use komodo_client::{
|
|
api::read::{
|
|
ExportAllResourcesToToml, ExportAllResourcesToTomlResponse,
|
|
ExportResourcesToToml, ExportResourcesToTomlResponse,
|
|
ListUserGroups,
|
|
},
|
|
entities::{
|
|
ResourceTarget, action::Action, alerter::Alerter, build::Build,
|
|
builder::Builder, deployment::Deployment,
|
|
permission::PermissionLevel, procedure::Procedure, repo::Repo,
|
|
resource::ResourceQuery, server::Server, stack::Stack,
|
|
sync::ResourceSync, toml::ResourcesToml, user::User,
|
|
},
|
|
};
|
|
use mungos::find::find_collect;
|
|
use resolver_api::Resolve;
|
|
|
|
use crate::{
|
|
helpers::query::{
|
|
get_all_tags, get_id_to_tags, get_user_user_group_ids,
|
|
},
|
|
permission::get_check_permissions,
|
|
resource,
|
|
state::db_client,
|
|
sync::{
|
|
toml::{ToToml, convert_resource},
|
|
user_groups::{convert_user_groups, user_group_to_toml},
|
|
variables::variable_to_toml,
|
|
},
|
|
};
|
|
|
|
use super::ReadArgs;
|
|
|
|
async fn get_all_targets(
|
|
tags: &[String],
|
|
user: &User,
|
|
) -> anyhow::Result<Vec<ResourceTarget>> {
|
|
let mut targets = Vec::<ResourceTarget>::new();
|
|
let all_tags = if tags.is_empty() {
|
|
vec![]
|
|
} else {
|
|
get_all_tags(None).await?
|
|
};
|
|
targets.extend(
|
|
resource::list_full_for_user::<Alerter>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Alerter(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Builder>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Builder(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Server>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Server(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Stack>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Stack(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Deployment>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Deployment(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Build>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Build(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Repo>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Repo(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Procedure>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Procedure(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<Action>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
.map(|resource| ResourceTarget::Action(resource.id)),
|
|
);
|
|
targets.extend(
|
|
resource::list_full_for_user::<ResourceSync>(
|
|
ResourceQuery::builder().tags(tags).build(),
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
&all_tags,
|
|
)
|
|
.await?
|
|
.into_iter()
|
|
// These will already be filtered by [ExportResourcesToToml]
|
|
.map(|resource| ResourceTarget::ResourceSync(resource.id)),
|
|
);
|
|
Ok(targets)
|
|
}
|
|
|
|
impl Resolve<ReadArgs> for ExportAllResourcesToToml {
|
|
async fn resolve(
|
|
self,
|
|
args: &ReadArgs,
|
|
) -> serror::Result<ExportAllResourcesToTomlResponse> {
|
|
let targets = if self.include_resources {
|
|
get_all_targets(&self.tags, &args.user).await?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
let user_groups = if self.include_user_groups {
|
|
if args.user.admin {
|
|
find_collect(&db_client().user_groups, None, None)
|
|
.await
|
|
.context("failed to query db for user groups")?
|
|
.into_iter()
|
|
.map(|user_group| user_group.id)
|
|
.collect()
|
|
} else {
|
|
get_user_user_group_ids(&args.user.id).await?
|
|
}
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
ExportResourcesToToml {
|
|
targets,
|
|
user_groups,
|
|
include_variables: self.include_variables,
|
|
}
|
|
.resolve(args)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl Resolve<ReadArgs> for ExportResourcesToToml {
|
|
async fn resolve(
|
|
self,
|
|
args: &ReadArgs,
|
|
) -> serror::Result<ExportResourcesToTomlResponse> {
|
|
let ExportResourcesToToml {
|
|
targets,
|
|
user_groups,
|
|
include_variables,
|
|
} = self;
|
|
let mut res = ResourcesToml::default();
|
|
let id_to_tags = get_id_to_tags(None).await?;
|
|
let ReadArgs { user } = args;
|
|
for target in targets {
|
|
match target {
|
|
ResourceTarget::Alerter(id) => {
|
|
let mut alerter = get_check_permissions::<Alerter>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Alerter::replace_ids(&mut alerter);
|
|
res.alerters.push(convert_resource::<Alerter>(
|
|
alerter,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::ResourceSync(id) => {
|
|
let mut sync = get_check_permissions::<ResourceSync>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
if sync.config.file_contents.is_empty()
|
|
&& (sync.config.files_on_host
|
|
|| !sync.config.repo.is_empty()
|
|
|| !sync.config.linked_repo.is_empty())
|
|
{
|
|
ResourceSync::replace_ids(&mut sync);
|
|
res.resource_syncs.push(convert_resource::<ResourceSync>(
|
|
sync,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
}
|
|
ResourceTarget::Server(id) => {
|
|
let mut server = get_check_permissions::<Server>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Server::replace_ids(&mut server);
|
|
res.servers.push(convert_resource::<Server>(
|
|
server,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Builder(id) => {
|
|
let mut builder = get_check_permissions::<Builder>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Builder::replace_ids(&mut builder);
|
|
res.builders.push(convert_resource::<Builder>(
|
|
builder,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Build(id) => {
|
|
let mut build = get_check_permissions::<Build>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Build::replace_ids(&mut build);
|
|
res.builds.push(convert_resource::<Build>(
|
|
build,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Deployment(id) => {
|
|
let mut deployment = get_check_permissions::<Deployment>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Deployment::replace_ids(&mut deployment);
|
|
res.deployments.push(convert_resource::<Deployment>(
|
|
deployment,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Repo(id) => {
|
|
let mut repo = get_check_permissions::<Repo>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Repo::replace_ids(&mut repo);
|
|
res.repos.push(convert_resource::<Repo>(
|
|
repo,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Stack(id) => {
|
|
let mut stack = get_check_permissions::<Stack>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Stack::replace_ids(&mut stack);
|
|
res.stacks.push(convert_resource::<Stack>(
|
|
stack,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
))
|
|
}
|
|
ResourceTarget::Procedure(id) => {
|
|
let mut procedure = get_check_permissions::<Procedure>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Procedure::replace_ids(&mut procedure);
|
|
res.procedures.push(convert_resource::<Procedure>(
|
|
procedure,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
));
|
|
}
|
|
ResourceTarget::Action(id) => {
|
|
let mut action = get_check_permissions::<Action>(
|
|
&id,
|
|
user,
|
|
PermissionLevel::Read.into(),
|
|
)
|
|
.await?;
|
|
Action::replace_ids(&mut action);
|
|
res.actions.push(convert_resource::<Action>(
|
|
action,
|
|
false,
|
|
vec![],
|
|
&id_to_tags,
|
|
));
|
|
}
|
|
ResourceTarget::System(_) => continue,
|
|
};
|
|
}
|
|
|
|
add_user_groups(user_groups, &mut res, args)
|
|
.await
|
|
.context("failed to add user groups")?;
|
|
|
|
if include_variables {
|
|
res.variables =
|
|
find_collect(&db_client().variables, None, None)
|
|
.await
|
|
.context("failed to get variables from db")?
|
|
.into_iter()
|
|
.map(|mut variable| {
|
|
if !user.admin && variable.is_secret {
|
|
variable.value = "#".repeat(variable.value.len())
|
|
}
|
|
variable
|
|
})
|
|
.collect();
|
|
}
|
|
|
|
let toml = serialize_resources_toml(res)
|
|
.context("failed to serialize resources to toml")?;
|
|
|
|
Ok(ExportResourcesToTomlResponse { toml })
|
|
}
|
|
}
|
|
|
|
async fn add_user_groups(
|
|
user_groups: Vec<String>,
|
|
res: &mut ResourcesToml,
|
|
args: &ReadArgs,
|
|
) -> anyhow::Result<()> {
|
|
let user_groups = ListUserGroups {}
|
|
.resolve(args)
|
|
.await
|
|
.map_err(|e| e.error)?
|
|
.into_iter()
|
|
.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, &mut ug).await?;
|
|
res.user_groups = ug.into_iter().map(|ug| ug.1).collect();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn serialize_resources_toml(
|
|
resources: ResourcesToml,
|
|
) -> anyhow::Result<String> {
|
|
let mut toml = String::new();
|
|
|
|
for server in resources.servers {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[server]]\n");
|
|
Server::push_to_toml_string(server, &mut toml)?;
|
|
}
|
|
|
|
for stack in resources.stacks {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[stack]]\n");
|
|
Stack::push_to_toml_string(stack, &mut toml)?;
|
|
}
|
|
|
|
for deployment in resources.deployments {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[deployment]]\n");
|
|
Deployment::push_to_toml_string(deployment, &mut toml)?;
|
|
}
|
|
|
|
for build in resources.builds {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[build]]\n");
|
|
Build::push_to_toml_string(build, &mut toml)?;
|
|
}
|
|
|
|
for repo in resources.repos {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[repo]]\n");
|
|
Repo::push_to_toml_string(repo, &mut toml)?;
|
|
}
|
|
|
|
for procedure in resources.procedures {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[procedure]]\n");
|
|
Procedure::push_to_toml_string(procedure, &mut toml)?;
|
|
}
|
|
|
|
for action in resources.actions {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[action]]\n");
|
|
Action::push_to_toml_string(action, &mut toml)?;
|
|
}
|
|
|
|
for alerter in resources.alerters {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[alerter]]\n");
|
|
Alerter::push_to_toml_string(alerter, &mut toml)?;
|
|
}
|
|
|
|
for builder in resources.builders {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[builder]]\n");
|
|
Builder::push_to_toml_string(builder, &mut toml)?;
|
|
}
|
|
|
|
for resource_sync in resources.resource_syncs {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str("[[resource_sync]]\n");
|
|
ResourceSync::push_to_toml_string(resource_sync, &mut toml)?;
|
|
}
|
|
|
|
for variable in &resources.variables {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str(&variable_to_toml(variable)?);
|
|
}
|
|
|
|
for user_group in resources.user_groups {
|
|
if !toml.is_empty() {
|
|
toml.push_str("\n\n##\n\n");
|
|
}
|
|
toml.push_str(&user_group_to_toml(user_group)?);
|
|
}
|
|
|
|
Ok(toml)
|
|
}
|