From 43593162b0a8ab5c77ee8c969045de6fb388d6f8 Mon Sep 17 00:00:00 2001 From: Maxwell Becker <49575486+mbecker20@users.noreply.github.com> Date: Sun, 18 Aug 2024 03:04:47 -0400 Subject: [PATCH] 1.13.2 local compose (#36) * stack config files_on_host * refresh stack cache not blocked when using files_on_host * add remote errors status * improve info tab * store the full path in ComposeContents --- bin/core/src/api/write/mod.rs | 12 +- bin/core/src/api/write/stack.rs | 74 ++++++++- bin/periphery/src/api/compose.rs | 62 +++++++- bin/periphery/src/api/mod.rs | 1 + bin/periphery/src/compose.rs | 61 ++++++-- client/core/rs/src/entities/stack.rs | 118 +++++++------- client/core/rs/src/entities/user.rs | 3 + client/core/ts/src/types.ts | 10 +- client/periphery/rs/src/api/compose.rs | 19 +++ frontend/src/components/config/index.tsx | 148 +++++++++--------- .../src/components/resources/stack/config.tsx | 71 ++++++++- .../src/components/resources/stack/index.tsx | 29 +++- .../src/components/resources/stack/info.tsx | 8 +- frontend/src/components/updates/details.tsx | 8 +- frontend/src/globals.css | 1 + frontend/src/lib/utils.ts | 14 ++ frontend/src/pages/resource.tsx | 2 +- runfile.toml | 20 ++- test.compose.yaml | 38 +++++ test.core.config.toml | 34 ++++ 20 files changed, 569 insertions(+), 164 deletions(-) create mode 100644 test.compose.yaml create mode 100644 test.core.config.toml diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index 4646ddff2..f93591ff1 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -3,6 +3,7 @@ use std::time::Instant; use anyhow::{anyhow, Context}; use axum::{middleware, routing::post, Extension, Router}; use axum_extra::{headers::ContentType, TypedHeader}; +use derive_variants::{EnumVariants, ExtractVariant}; use monitor_client::{api::write::*, entities::user::User}; use resolver_api::{derive::Resolver, Resolver}; use serde::{Deserialize, Serialize}; @@ -31,7 +32,10 @@ mod user_group; mod variable; #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, +)] +#[variant_derive(Debug)] #[resolver_target(State)] #[resolver_args(User)] #[serde(tag = "type", content = "params")] @@ -178,7 +182,11 @@ async fn handler( Ok((TypedHeader(ContentType::json()), res??)) } -#[instrument(name = "WriteRequest", skip(user), fields(user_id = user.id))] +#[instrument( + name = "WriteRequest", + skip(user, request), + fields(user_id = user.id, request = format!("{:?}", request.extract_variant())) +)] async fn task( req_id: Uuid, request: WriteRequest, diff --git a/bin/core/src/api/write/stack.rs b/bin/core/src/api/write/stack.rs index 524099c31..fc963e4e4 100644 --- a/bin/core/src/api/write/stack.rs +++ b/bin/core/src/api/write/stack.rs @@ -1,11 +1,13 @@ use anyhow::{anyhow, Context}; +use formatting::format_serror; use monitor_client::{ api::write::*, entities::{ config::core::CoreConfig, monitor_timestamp, permission::PermissionLevel, - stack::{PartialStackConfig, Stack, StackInfo}, + server::ServerState, + stack::{ComposeContents, PartialStackConfig, Stack, StackInfo}, update::Update, user::User, NoData, Operation, @@ -18,11 +20,16 @@ use mungos::{ use octorust::types::{ ReposCreateWebhookRequest, ReposCreateWebhookRequestConfig, }; +use periphery_client::api::compose::{ + GetComposeContentsOnHost, GetComposeContentsOnHostResponse, +}; use resolver_api::Resolve; use crate::{ config::core_config, helpers::{ + periphery_client, + query::get_server_with_status, stack::{ remote::get_remote_compose_contents, services::extract_services_into_res, @@ -146,7 +153,10 @@ impl Resolve for State { let file_contents_empty = stack.config.file_contents.is_empty(); - if file_contents_empty && stack.config.repo.is_empty() { + if !stack.config.files_on_host + && file_contents_empty + && stack.config.repo.is_empty() + { // Nothing to do without one of these return Ok(NoData {}); } @@ -159,8 +169,63 @@ impl Resolve for State { remote_errors, latest_hash, latest_message, - ) = if file_contents_empty { + ) = if stack.config.files_on_host { + // ============= + // FILES ON HOST + // ============= + if stack.config.server_id.is_empty() { + (vec![], None, None, None, None) + } else { + let (server, status) = + get_server_with_status(&stack.config.server_id).await?; + if status != ServerState::Ok { + (vec![], None, None, None, None) + } else { + let GetComposeContentsOnHostResponse { contents, errors } = + match periphery_client(&server)? + .request(GetComposeContentsOnHost { + file_paths: stack.file_paths().to_vec(), + name: stack.name.clone(), + run_directory: stack.config.run_directory.clone(), + }) + .await + .context( + "failed to get compose file contents from host", + ) { + Ok(res) => res, + Err(e) => GetComposeContentsOnHostResponse { + contents: Default::default(), + errors: vec![ComposeContents { + path: stack.config.run_directory.clone(), + contents: format_serror(&e.into()), + }], + }, + }; + + let project_name = stack.project_name(true); + + let mut services = Vec::new(); + + for contents in &contents { + if let Err(e) = extract_services_into_res( + &project_name, + &contents.contents, + &mut services, + ) { + warn!( + "failed to extract stack services, things won't works correctly. stack: {} | {e:#}", + stack.name + ); + } + } + + (services, Some(contents), Some(errors), None, None) + } + } + } else if file_contents_empty { + // ================ // REPO BASED STACK + // ================ let ( remote_contents, remote_errors, @@ -196,6 +261,9 @@ impl Resolve for State { latest_message, ) } else { + // ============= + // UI BASED FILE + // ============= let mut services = Vec::new(); if let Err(e) = extract_services_into_res( // this should latest (not deployed), so make the project name fresh. diff --git a/bin/periphery/src/api/compose.rs b/bin/periphery/src/api/compose.rs index e25938993..cb72a26c2 100644 --- a/bin/periphery/src/api/compose.rs +++ b/bin/periphery/src/api/compose.rs @@ -1,13 +1,21 @@ +use std::path::PathBuf; + use anyhow::{anyhow, Context}; use command::run_monitor_command; use formatting::format_serror; -use monitor_client::entities::{stack::ComposeProject, update::Log}; +use monitor_client::entities::{ + stack::{ComposeContents, ComposeProject}, + to_monitor_name, + update::Log, +}; use periphery_client::api::compose::*; use resolver_api::Resolve; use serde::{Deserialize, Serialize}; +use tokio::fs; use crate::{ compose::{compose_up, docker_compose}, + config::periphery_config, helpers::log_grep, State, }; @@ -70,6 +78,58 @@ pub struct DockerComposeLsItem { // +impl Resolve for State { + async fn resolve( + &self, + GetComposeContentsOnHost { + name, + run_directory, + file_paths, + }: GetComposeContentsOnHost, + _: (), + ) -> anyhow::Result { + let root = + periphery_config().stack_dir.join(to_monitor_name(&name)); + let run_directory = root.join(&run_directory); + let run_directory = run_directory.canonicalize().context( + "failed to validate run directory on host (canonicalize error)", + )?; + + let file_paths = file_paths + .iter() + .map(|path| { + run_directory.join(path).components().collect::() + }) + .collect::>(); + + let mut res = GetComposeContentsOnHostResponse::default(); + + for full_path in &file_paths { + match fs::read_to_string(&full_path).await.with_context(|| { + format!( + "failed to read compose file contents at {full_path:?}" + ) + }) { + Ok(contents) => { + res.contents.push(ComposeContents { + path: full_path.display().to_string(), + contents, + }); + } + Err(e) => { + res.errors.push(ComposeContents { + path: full_path.display().to_string(), + contents: format_serror(&e.into()), + }); + } + } + } + Ok(res) + } +} + +// + impl Resolve for State { #[instrument( name = "GetComposeServiceLog", diff --git a/bin/periphery/src/api/mod.rs b/bin/periphery/src/api/mod.rs index 00007f23e..f22f3518b 100644 --- a/bin/periphery/src/api/mod.rs +++ b/bin/periphery/src/api/mod.rs @@ -91,6 +91,7 @@ pub enum PeripheryRequest { // Compose ListComposeProjects(ListComposeProjects), + GetComposeContentsOnHost(GetComposeContentsOnHost), GetComposeServiceLog(GetComposeServiceLog), GetComposeServiceLogSearch(GetComposeServiceLogSearch), ComposeUp(ComposeUp), diff --git a/bin/periphery/src/compose.rs b/bin/periphery/src/compose.rs index 80f42d3c0..9d84a6f1c 100644 --- a/bin/periphery/src/compose.rs +++ b/bin/periphery/src/compose.rs @@ -58,7 +58,13 @@ pub async fn compose_up( let file_paths = stack .file_paths() .iter() - .map(|path| (path, run_directory.join(path))) + .map(|path| { + ( + path, + // This will remove any intermediate uneeded '/./' in the path + run_directory.join(path).components().collect::(), + ) + }) .collect::>(); for (path, full_path) in &file_paths { @@ -70,7 +76,7 @@ pub async fn compose_up( return Err(anyhow!("A compose file doesn't exist after writing stack. Ensure the run_directory and file_paths are correct.")); } - for (path, full_path) in &file_paths { + for (_, full_path) in &file_paths { let file_contents = match fs::read_to_string(&full_path).await.with_context(|| { format!( @@ -85,7 +91,7 @@ pub async fn compose_up( .push(Log::error("read compose file", error.clone())); // This should only happen for repo stacks, ie remote error res.remote_errors.push(ComposeContents { - path: path.to_string(), + path: full_path.display().to_string(), contents: error, }); return Err(anyhow!( @@ -172,12 +178,17 @@ pub async fn compose_up( res.deployed = log.success; res.logs.push(log); - if let Err(e) = fs::remove_dir_all(&root).await.with_context(|| { - format!("failed to clean up files after deploy | path: {root:?} | ensure all volumes are mounted outside the repo directory (preferably use absolute path for mounts)") - }) { - res - .logs - .push(Log::error("clean up files", format_serror(&e.into()))) + // Unless the files are supposed to be managed on the host, + // clean up here, which will also let user know immediately if there will be a problem + // with any accidental volumes mounted inside repo directory (just use absolute volumes anyways) + if !stack.config.files_on_host { + if let Err(e) = fs::remove_dir_all(&root).await.with_context(|| { + format!("failed to clean up files after deploy | path: {root:?} | ensure all volumes are mounted outside the repo directory (preferably use absolute path for mounts)") + }) { + res + .logs + .push(Log::error("clean up files", format_serror(&e.into()))) + } } Ok(()) @@ -198,8 +209,33 @@ async fn write_stack( // Cannot use canonicalize yet as directory may not exist. let run_directory = run_directory.components().collect::(); - if stack.config.file_contents.is_empty() { - // Clone the repo + if stack.config.files_on_host { + // ============= + // FILES ON HOST + // ============= + // Only need to write environment file here (which does nothing if not using this feature) + let env_file_path = match write_environment_file( + &stack.config.environment, + &stack.config.env_file_path, + stack + .config + .skip_secret_interp + .then_some(&periphery_config().secrets), + &run_directory, + &mut res.logs, + ) + .await + { + Ok(path) => path, + Err(_) => { + return Err(anyhow!("failed to write environment file")); + } + }; + Ok(env_file_path) + } else if stack.config.file_contents.is_empty() { + // ================ + // REPO BASED FILES + // ================ if stack.config.repo.is_empty() { // Err response will be written to return, no need to add it to log here return Err(anyhow!("Must either input compose file contents directly or provide a repo. Got neither.")); @@ -284,6 +320,9 @@ async fn write_stack( Ok(env_file_path) } else { + // ============== + // UI BASED FILES + // ============== // Ensure run directory exists fs::create_dir_all(&run_directory).await.with_context(|| { format!( diff --git a/client/core/rs/src/entities/stack.rs b/client/core/rs/src/entities/stack.rs index 9e7bbd328..2e5549662 100644 --- a/client/core/rs/src/entities/stack.rs +++ b/client/core/rs/src/entities/stack.rs @@ -153,7 +153,8 @@ pub struct StackInfo { #[serde(default)] pub latest_services: Vec, - /// The remote compose file contents. This is updated whenever Monitor refreshes the stack cache. + /// The remote compose file contents, whether on host or in repo. + /// This is updated whenever Monitor refreshes the stack cache. /// It will be empty if the file is defined directly in the stack config. pub remote_contents: Option>, /// If there was an error in getting the remote contents, it will be here. @@ -165,59 +166,6 @@ pub struct StackInfo { pub latest_message: Option, } -#[typeshare] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct ComposeProject { - /// The compose project name. - pub name: String, - /// The status of the project, as returned by docker. - pub status: Option, - /// The compose files included in the project. - pub compose_files: Vec, -} - -#[typeshare] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct ComposeContents { - /// The path of the file on the host - pub path: String, - /// The contents of the file - pub contents: String, -} - -#[typeshare] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct StackServiceNames { - /// The name of the service - pub service_name: String, - /// Will either be the declared container_name in the compose file, - /// or a pattern to match auto named containers. - /// - /// Auto named containers are composed of three parts: - /// - /// 1. The name of the compose project (top level name field of compose file). - /// This defaults to the name of the parent folder of the compose file. - /// Monitor will always set it to be the name of the stack, but imported stacks - /// will have a different name. - /// 2. The service name - /// 3. The replica number - /// - /// Example: stacko-mongo-1. - /// - /// This stores only 1. and 2., ie stacko-mongo. - /// Containers will be matched via regex like `^container_name-?[0-9]*$`` - pub container_name: String, -} - -#[typeshare] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct StackService { - /// The service name - pub service: String, - /// The container - pub container: Option, -} - #[typeshare(serialized_as = "Partial")] pub type _PartialStackConfig = PartialStackConfig; @@ -254,6 +202,14 @@ pub struct StackConfig { #[builder(default)] pub file_paths: Vec, + /// If this is checked, the stack will source the files on the host. + /// Use `run_directory` and `file_paths` to specify the path on the host. + /// This is useful for those who wish to setup their files on the host using SSH or similar, + /// rather than defining the contents in UI or in a git repo. + #[serde(default)] + #[builder(default)] + pub files_on_host: bool, + /// Used with `registry_account` to login to a registry before docker compose up. #[serde(default)] #[builder(default)] @@ -412,6 +368,7 @@ impl Default for StackConfig { project_name: Default::default(), run_directory: default_run_directory(), file_paths: Default::default(), + files_on_host: Default::default(), registry_provider: Default::default(), registry_account: Default::default(), file_contents: Default::default(), @@ -433,6 +390,59 @@ impl Default for StackConfig { } } +#[typeshare] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ComposeProject { + /// The compose project name. + pub name: String, + /// The status of the project, as returned by docker. + pub status: Option, + /// The compose files included in the project. + pub compose_files: Vec, +} + +#[typeshare] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ComposeContents { + /// The path of the file on the host + pub path: String, + /// The contents of the file + pub contents: String, +} + +#[typeshare] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct StackServiceNames { + /// The name of the service + pub service_name: String, + /// Will either be the declared container_name in the compose file, + /// or a pattern to match auto named containers. + /// + /// Auto named containers are composed of three parts: + /// + /// 1. The name of the compose project (top level name field of compose file). + /// This defaults to the name of the parent folder of the compose file. + /// Monitor will always set it to be the name of the stack, but imported stacks + /// will have a different name. + /// 2. The service name + /// 3. The replica number + /// + /// Example: stacko-mongo-1. + /// + /// This stores only 1. and 2., ie stacko-mongo. + /// Containers will be matched via regex like `^container_name-?[0-9]*$`` + pub container_name: String, +} + +#[typeshare] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct StackService { + /// The service name + pub service: String, + /// The container + pub container: Option, +} + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] pub struct StackActionState { diff --git a/client/core/rs/src/entities/user.rs b/client/core/rs/src/entities/user.rs index 29f1912bb..73bcb69e5 100644 --- a/client/core/rs/src/entities/user.rs +++ b/client/core/rs/src/entities/user.rs @@ -78,6 +78,9 @@ impl User { } } + /// Returns whether user is an inbuilt service user + /// + /// NOTE: ALSO UPDATE `frontend/src/lib/utils/is_service_user` to match pub fn is_service_user(user_id: &str) -> bool { matches!( user_id, diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 59ab8b00b..014f8ef65 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -1481,6 +1481,13 @@ export interface StackConfig { * If this is empty, will use file `compose.yaml`. */ file_paths?: string[]; + /** + * If this is checked, the stack will source the files on the host. + * Use `run_directory` and `file_paths` to specify the path on the host. + * This is useful for those who wish to setup their files on the host using SSH or similar, + * rather than defining the contents in UI or in a git repo. + */ + files_on_host?: boolean; /** Used with `registry_account` to login to a registry before docker compose up. */ registry_provider?: string; /** Used with `registry_provider` to login to a registry before docker compose up. */ @@ -1612,7 +1619,8 @@ export interface StackInfo { */ latest_services?: StackServiceNames[]; /** - * The remote compose file contents. This is updated whenever Monitor refreshes the stack cache. + * The remote compose file contents, whether on host or in repo. + * This is updated whenever Monitor refreshes the stack cache. * It will be empty if the file is defined directly in the stack config. */ remote_contents?: ComposeContents[]; diff --git a/client/periphery/rs/src/api/compose.rs b/client/periphery/rs/src/api/compose.rs index 2831d8223..28c7e5993 100644 --- a/client/periphery/rs/src/api/compose.rs +++ b/client/periphery/rs/src/api/compose.rs @@ -17,6 +17,25 @@ pub struct ListComposeProjects {} // +/// Get the compose contents on the host, for stacks using +/// `files_on_host`. +#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[response(GetComposeContentsOnHostResponse)] +pub struct GetComposeContentsOnHost { + /// The name of the stack + pub name: String, + pub run_directory: String, + pub file_paths: Vec, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct GetComposeContentsOnHostResponse { + pub contents: Vec, + pub errors: Vec, +} + +// + /// The stack folder must already exist for this to work #[derive(Debug, Clone, Serialize, Deserialize, Request)] #[response(Log)] diff --git a/frontend/src/components/config/index.tsx b/frontend/src/components/config/index.tsx index 183d226cd..7b746bb6e 100644 --- a/frontend/src/components/config/index.tsx +++ b/frontend/src/components/config/index.tsx @@ -125,7 +125,7 @@ export const Config = ({ titleOther?: ReactNode; components: Record< string, // sidebar key - ConfigComponent[] + ConfigComponent[] | false | undefined >; }) => { const [show, setShow] = useState(keys(components)[0]); @@ -163,80 +163,84 @@ export const Config = ({
{/** The sidebar when large */}
- {keys(components).map((tab) => ( - - ))} + {Object.entries(components) + .filter(([_, val]) => val) + .map(([tab, _]) => ( + + ))}
-
- {components[show].map( - ({ - label, - labelHidden, - icon, - actions, - description, - hidden, - contentHidden, - components, - }) => - !hidden && ( - - {!labelHidden && ( - -
- - {icon} - {label} - - {description && ( - - - - - - - - {description} - - + {components[show] && ( +
+ {components[show].map( + ({ + label, + labelHidden, + icon, + actions, + description, + hidden, + contentHidden, + components, + }) => + !hidden && ( + + {!labelHidden && ( + - {actions} - - )} - {!contentHidden && ( - - set((p) => ({ ...p, ...u }))} - components={components} - disabled={disabled} - /> - - )} - - ) - )} -
+ > +
+ + {icon} + {label} + + {description && ( + + + + + + + + {description} + + + )} +
+ {actions} + + )} + {!contentHidden && ( + + set((p) => ({ ...p, ...u }))} + components={components} + disabled={disabled} + /> + + )} + + ) + )} +
+ )}
); diff --git a/frontend/src/components/resources/stack/config.tsx b/frontend/src/components/resources/stack/config.tsx index 93dfc030e..fccc7d2f7 100644 --- a/frontend/src/components/resources/stack/config.tsx +++ b/frontend/src/components/resources/stack/config.tsx @@ -48,6 +48,8 @@ export const StackConfig = ({ const disabled = global_disabled || perms !== Types.PermissionLevel.Write; + const files_on_host = update.files_on_host ?? config.files_on_host; + return ( + set((update) => ({ + ...update, + file_paths: [ + ...(update.file_paths ?? config.file_paths ?? []), + "", + ], + })) + } + className="flex items-center gap-2 w-[200px]" + > + + Add File + + ), + components: { + file_paths: (value, set) => ( + + ), + }, + }, { label: "Compose File", + hidden: files_on_host, description: "Paste the file contents here, or configure a git repo.", actions: ( @@ -252,7 +317,7 @@ export const StackConfig = ({ }, }, ], - "Git Repo": [ + "Git Repo": !files_on_host && [ { label: "Git", description: @@ -307,7 +372,7 @@ export const StackConfig = ({ labelHidden: true, components: { run_directory: { - placeholder: "Eg. './'", + placeholder: "./", description: "Set the cwd when running compose up command. Relative to the repo root.", boldLabel: true, @@ -317,7 +382,7 @@ export const StackConfig = ({ { label: "File Paths", description: - "Add files to include using 'docker compose -f'. If empty, uses 'compose.yaml'.", + "Add files to include using 'docker compose -f'. If empty, uses 'compose.yaml'. Relative to 'Run Directory'.", contentHidden: (update.file_paths ?? config.file_paths)?.length === 0, actions: !disabled && ( diff --git a/frontend/src/components/resources/stack/index.tsx b/frontend/src/components/resources/stack/index.tsx index 41a7cf2f8..1bea8c515 100644 --- a/frontend/src/components/resources/stack/index.tsx +++ b/frontend/src/components/resources/stack/index.tsx @@ -53,7 +53,8 @@ const StackIcon = ({ id, size }: { id?: string; size: number }) => { const ConfigInfoServices = ({ id }: { id: string }) => { const [view, setView] = useLocalStorage("stack-tabs-v1", "Config"); - const state = useStack(id)?.info.state; + const info = useStack(id)?.info; + const state = info?.state; const stackDown = state === undefined || state === Types.StackState.Unknown || @@ -150,7 +151,7 @@ export const StackComponents: RequiredResourceComponents = { }, NoConfig: ({ id }) => { const config = useFullStack(id)?.config; - if (config?.file_contents || config?.repo) { + if (config?.files_on_host || config?.file_contents || config?.repo) { return null; } return ( @@ -200,6 +201,30 @@ export const StackComponents: RequiredResourceComponents = { ); }, + RemoteErrors: ({ id }) => { + const info = useFullStack(id)?.info; + const errors = info?.remote_errors; + if (!errors || errors.length === 0) { + return null; + } + return ( + + + +
+ Remote Error +
+
+
+ +
+ There are errors reading the remote file contents. See{" "} + Info tab for details. +
+
+
+ ); + }, Deployed: ({ id }) => { const info = useStack(id)?.info; const fullInfo = useFullStack(id)?.info; diff --git a/frontend/src/components/resources/stack/info.tsx b/frontend/src/components/resources/stack/info.tsx index 3e18763f7..eceac57a3 100644 --- a/frontend/src/components/resources/stack/info.tsx +++ b/frontend/src/components/resources/stack/info.tsx @@ -3,6 +3,7 @@ import { ReactNode } from "react"; import { Card, CardHeader } from "@ui/card"; import { useFullStack, useStack } from "."; import { Types } from "@monitor/client"; +import { updateLogToHtml } from "@lib/utils"; export const StackInfo = ({ id, @@ -65,7 +66,12 @@ export const StackInfo = ({ {stack?.info?.remote_errors?.map((content, i) => (
                 path: {content.path}
-                
{content.contents}
+
               
))} diff --git a/frontend/src/components/updates/details.tsx b/frontend/src/components/updates/details.tsx index 45eb75c70..a376797fc 100644 --- a/frontend/src/components/updates/details.tsx +++ b/frontend/src/components/updates/details.tsx @@ -28,6 +28,7 @@ import { Link } from "react-router-dom"; import { fmt_duration, fmt_operation, fmt_version } from "@lib/formatting"; import { cn, + is_service_user, updateLogToHtml, usableResourcePath, version_is_none, @@ -50,12 +51,7 @@ export const UpdateUser = ({ defaultAvatar?: boolean; muted?: boolean; }) => { - if ( - user_id === "Procedure" || - user_id === "Github" || - user_id === "Auto Redeploy" || - user_id === "Resource Sync" - ) { + if (is_service_user(user_id)) { return (
{ !pending.data.user_group_updates ); }; + +export const is_service_user = (user_id: string) => { + return ( + user_id === "System" || + user_id === "Procedure" || + user_id === "Github" || + user_id === "Git Webhook" || + user_id === "Auto Redeploy" || + user_id === "Resource Sync" || + user_id === "Stack Wizard" || + user_id === "Build Manager" || + user_id === "Repo Manager" + ); +}; diff --git a/frontend/src/pages/resource.tsx b/frontend/src/pages/resource.tsx index c0e240e86..65786d3cb 100644 --- a/frontend/src/pages/resource.tsx +++ b/frontend/src/pages/resource.tsx @@ -129,7 +129,7 @@ const ResourceHeader = ({ type, id }: { type: UsableResource; id: string }) => {
-

Description:

+ {/*

Description:

*/}
diff --git a/runfile.toml b/runfile.toml index 59c21fc98..7988201d2 100644 --- a/runfile.toml +++ b/runfile.toml @@ -15,6 +15,16 @@ cmd = "yarn dev" path = "frontend" cmd = "yarn build" +[test-compose] +description = "deploys test.compose.yaml" +cmd = """ +docker compose -f test.compose.yaml down && \ +docker compose -f test.compose.yaml up --attach monitor-periphery""" + +[test-core] +description = "runs core --release pointing to test.core.config.toml" +cmd = "MONITOR_CONFIG_PATH=test.core.config.toml cargo run -p monitor_core --release" + [update-periphery] path = "." cmd = """ @@ -24,13 +34,6 @@ cp ./target/release/periphery /usr/local/bin/periphery && \ chmod +x /usr/local/bin/periphery && \ systemctl start periphery""" -[monitor-cli-test] -path = "bin/cli" -cmd = "cargo run -- execute --help" - -[rustdoc-server] -cmd = "cargo watch -s 'cargo doc --no-deps -p monitor_client' & http --quiet target/doc" - [docsite-start] path = "docsite" cmd = "yarn start" @@ -38,3 +41,6 @@ cmd = "yarn start" [docsite-deploy] path = "docsite" cmd = "yarn deploy" + +[rustdoc-server] +cmd = "cargo watch -s 'cargo doc --no-deps -p monitor_client' & http --quiet target/doc" \ No newline at end of file diff --git a/test.compose.yaml b/test.compose.yaml new file mode 100644 index 000000000..d232d32b9 --- /dev/null +++ b/test.compose.yaml @@ -0,0 +1,38 @@ +services: + monitor-periphery: + build: + context: . + dockerfile: bin/periphery/Dockerfile + logging: + driver: local + networks: + - monitor-network + ports: + - 8120:8120 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - monitor-repos:/etc/monitor/repos + # environment: + # # If the disk size is overreporting, can use one of these to + # # whitelist / blacklist the disks to filter them, whichever is easier. + # PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/monitor/repos + # PERIPHERY_EXCLUDE_DISK_MOUNTS: /snap + + monitor-mongo: + image: mongo + restart: unless-stopped + logging: + driver: none + networks: + - monitor-network + ports: + - 27017:27017 + volumes: + - db-data:/data/db + +volumes: + db-data: + monitor-repos: + +networks: + monitor-network: {} \ No newline at end of file diff --git a/test.core.config.toml b/test.core.config.toml new file mode 100644 index 000000000..a888c8aa6 --- /dev/null +++ b/test.core.config.toml @@ -0,0 +1,34 @@ +title = "Test" +host = "http://localhost.9120" +passkey = "a_random_passkey" +ensure_server = "http://localhost:8120" + +############ +# DATABASE # +############ + +mongo.address = "localhost:27017" + +################ +# AUTH / LOGIN # +################ + +local_auth = true +jwt_secret = "your_random_secret" +jwt_ttl = "2-wk" + +############ +# WEBHOOKS # +############ + +webhook_secret = "a_random_webhook_secret" + +################## +# POLL INTERVALS # +################## + +stack_poll_interval = "1-min" +sync_poll_interval = "1-min" +build_poll_interval = "1-min" +repo_poll_interval = "1-min" +monitoring_interval = "5-sec"