mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-12 18:42:39 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2463ed3879 | ||
|
|
a2758ce6f4 | ||
|
|
3f1788dbbb | ||
|
|
33a0560af6 | ||
|
|
610a10c488 | ||
|
|
39b217687d | ||
|
|
2f73461979 | ||
|
|
aae9bb9e51 | ||
|
|
7d011d93fa | ||
|
|
bffdea4357 | ||
|
|
790566bf79 | ||
|
|
b17db93f13 |
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -41,7 +41,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -922,7 +922,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
@@ -1306,7 +1306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1437,7 +1437,7 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2041,7 +2041,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2111,7 +2111,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2164,9 +2164,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mongo_indexed"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2570cf8b41b536db37f64a27bafb814e3f1e58db5266dd295427364d957e974f"
|
||||
checksum = "556e2883109599e3cc28c7ad0d700c0b5c297e4d17ae810c669d18f79a02df26"
|
||||
dependencies = [
|
||||
"mongo_indexed_derive",
|
||||
"mongodb",
|
||||
@@ -2246,7 +2246,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2262,7 +2262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2294,7 +2294,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2349,7 +2349,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2773,7 +2773,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -4507,7 +4507,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
|
||||
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.13.2"
|
||||
version = "1.13.4"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,7 +15,7 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.13.2"
|
||||
monitor_client = "1.13.3"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
command = { path = "lib/command" }
|
||||
@@ -32,7 +32,7 @@ merge_config_files = "0.1.5"
|
||||
async_timing_util = "1.0.0"
|
||||
partial_derive2 = "0.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "2.0.0"
|
||||
mongo_indexed = "2.0.1"
|
||||
resolver_api = "1.1.1"
|
||||
toml_pretty = "1.1.2"
|
||||
mungos = "1.0.1"
|
||||
|
||||
@@ -122,22 +122,6 @@ pub async fn compose_up(
|
||||
let last_project_name = stack.project_name(false);
|
||||
let project_name = stack.project_name(true);
|
||||
|
||||
// Pull images before destroying to minimize downtime.
|
||||
// If this fails, do not continue.
|
||||
let log = run_monitor_command(
|
||||
"compose pull",
|
||||
format!(
|
||||
"cd {run_dir} && {docker_compose} -p {project_name} -f {file_args} pull{service_arg}",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
if !log.success {
|
||||
res.logs.push(log);
|
||||
return Err(anyhow!(
|
||||
"Failed to pull required images, stopping the run."
|
||||
));
|
||||
}
|
||||
|
||||
// Login to the registry to pull private images, if account is set
|
||||
if !stack.config.registry_account.is_empty() {
|
||||
let registry = ImageRegistry::Standard(StandardRegistryConfig {
|
||||
@@ -157,6 +141,22 @@ pub async fn compose_up(
|
||||
.context("failed to login to image registry")?;
|
||||
}
|
||||
|
||||
// Pull images before destroying to minimize downtime.
|
||||
// If this fails, do not continue.
|
||||
let log = run_monitor_command(
|
||||
"compose pull",
|
||||
format!(
|
||||
"cd {run_dir} && {docker_compose} -p {project_name} -f {file_args} pull{service_arg}",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
if !log.success {
|
||||
res.logs.push(log);
|
||||
return Err(anyhow!(
|
||||
"Failed to pull required images, stopping the run."
|
||||
));
|
||||
}
|
||||
|
||||
// Take down the existing containers.
|
||||
// This one tries to use the previously deployed service name, to ensure the right stack is taken down.
|
||||
destroy_existing_containers(&last_project_name, service, res)
|
||||
|
||||
@@ -107,7 +107,7 @@ impl StatsClient {
|
||||
.list()
|
||||
.iter()
|
||||
.filter(|d| {
|
||||
if d.file_system() != "overlay" {
|
||||
if d.file_system() == "overlay" {
|
||||
return false;
|
||||
}
|
||||
let path = d.mount_point();
|
||||
|
||||
@@ -43,8 +43,8 @@ services:
|
||||
image: ghcr.io/mbecker20/periphery:latest # use ghcr.io/mbecker20/periphery:latest-aarch64 for arm support
|
||||
logging:
|
||||
driver: local
|
||||
ports:
|
||||
- 8120:8120
|
||||
ports:
|
||||
- 8120:8120
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- monitor-repos:/etc/monitor/repos # manage repos in a docker volume, or change it to an accessible host directory.
|
||||
|
||||
@@ -20,6 +20,11 @@ With Monitor you can:
|
||||
|
||||
Monitor is opinionated by design, and uses [docker](https://docs.docker.com/) as the container engine for building and deploying.
|
||||
|
||||
:::info
|
||||
Monitor also supports [**podman**](https://podman.io/) instead of docker by utilizing the `podman` -> `docker` alias.
|
||||
For Stack / docker compose support with podman, check out [**podman-compose**](https://github.com/containers/podman-compose). Thanks to `u/pup_kit` for checking this.
|
||||
:::
|
||||
|
||||
## Architecture and Components
|
||||
|
||||
Monitor is composed of a single core and any amount of connected servers running the periphery application.
|
||||
|
||||
@@ -72,7 +72,7 @@ export const BuildConfig = ({
|
||||
version: (_version, set) => {
|
||||
const version =
|
||||
typeof _version === "object"
|
||||
? `${_version.major}.${_version.major}.${_version.patch}`
|
||||
? `${_version.major}.${_version.minor}.${_version.patch}`
|
||||
: _version;
|
||||
return (
|
||||
<ConfigInput
|
||||
@@ -136,6 +136,14 @@ export const BuildConfig = ({
|
||||
{
|
||||
label: "Image",
|
||||
components: {
|
||||
image_registry: (registry, set) => (
|
||||
<ImageRegistryConfig
|
||||
registry={registry}
|
||||
setRegistry={(image_registry) => set({ image_registry })}
|
||||
resource_id={update.builder_id ?? config.builder_id}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
build_path: {
|
||||
placeholder: ".",
|
||||
description:
|
||||
@@ -146,14 +154,6 @@ export const BuildConfig = ({
|
||||
description:
|
||||
"The path to the dockerfile, relative to the build path.",
|
||||
},
|
||||
image_registry: (registry, set) => (
|
||||
<ImageRegistryConfig
|
||||
registry={registry}
|
||||
setRegistry={(image_registry) => set({ image_registry })}
|
||||
resource_id={update.builder_id ?? config.builder_id}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,8 +47,9 @@ export const StackConfig = ({
|
||||
if (!config) return null;
|
||||
|
||||
const disabled = global_disabled || perms !== Types.PermissionLevel.Write;
|
||||
|
||||
const files_on_host = update.files_on_host ?? config.files_on_host;
|
||||
const ui_file_contents =
|
||||
(update.file_contents ?? config.file_contents ?? "").length > 0;
|
||||
|
||||
return (
|
||||
<Config
|
||||
@@ -283,7 +284,7 @@ export const StackConfig = ({
|
||||
description:
|
||||
"If your compose file has init services that exit early, ignore them here so your stack will report the correct health.",
|
||||
contentHidden:
|
||||
((update.extra_args ?? config.extra_args)?.length ?? 0) === 0,
|
||||
((update.ignore_services ?? config.ignore_services)?.length ?? 0) === 0,
|
||||
actions: !disabled && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -317,274 +318,282 @@ export const StackConfig = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
"Git Repo": !files_on_host && [
|
||||
{
|
||||
label: "Git",
|
||||
description:
|
||||
"Provide config for repo-based compose files. Not necessary if file contents are configured in UI.",
|
||||
components: {
|
||||
git_provider: (provider, set) => {
|
||||
const https = update.git_https ?? config.git_https;
|
||||
return (
|
||||
<ProviderSelectorConfig
|
||||
account_type="git"
|
||||
selected={provider}
|
||||
disabled={disabled}
|
||||
onSelect={(git_provider) => set({ git_provider })}
|
||||
https={https}
|
||||
onHttpsSwitch={() => set({ git_https: !https })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
git_account: (value, set) => {
|
||||
const server_id = update.server_id || config.server_id;
|
||||
return (
|
||||
<AccountSelectorConfig
|
||||
id={server_id}
|
||||
type={server_id ? "Server" : "None"}
|
||||
account_type="git"
|
||||
provider={update.git_provider ?? config.git_provider}
|
||||
selected={value}
|
||||
onSelect={(git_account) => set({ git_account })}
|
||||
disabled={disabled}
|
||||
placeholder="None"
|
||||
/>
|
||||
);
|
||||
},
|
||||
repo: {
|
||||
placeholder: "Enter repo",
|
||||
description:
|
||||
"The repo path on the provider. {namespace}/{repo_name}",
|
||||
},
|
||||
branch: {
|
||||
placeholder: "Enter branch",
|
||||
description: "Select a custom branch, or default to 'main'.",
|
||||
},
|
||||
commit: {
|
||||
placeholder: "Enter a specific commit hash. Optional.",
|
||||
description:
|
||||
"Switch to a specific hash after cloning the branch.",
|
||||
"Git Repo": !files_on_host &&
|
||||
!ui_file_contents && [
|
||||
{
|
||||
label: "Git",
|
||||
description:
|
||||
"Provide config for repo-based compose files. Not necessary if file contents are configured in UI.",
|
||||
components: {
|
||||
git_provider: (provider, set) => {
|
||||
const https = update.git_https ?? config.git_https;
|
||||
return (
|
||||
<ProviderSelectorConfig
|
||||
account_type="git"
|
||||
selected={provider}
|
||||
disabled={disabled}
|
||||
onSelect={(git_provider) => set({ git_provider })}
|
||||
https={https}
|
||||
onHttpsSwitch={() => set({ git_https: !https })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
git_account: (value, set) => {
|
||||
const server_id = update.server_id || config.server_id;
|
||||
return (
|
||||
<AccountSelectorConfig
|
||||
id={server_id}
|
||||
type={server_id ? "Server" : "None"}
|
||||
account_type="git"
|
||||
provider={update.git_provider ?? config.git_provider}
|
||||
selected={value}
|
||||
onSelect={(git_account) => set({ git_account })}
|
||||
disabled={disabled}
|
||||
placeholder="None"
|
||||
/>
|
||||
);
|
||||
},
|
||||
repo: {
|
||||
placeholder: "Enter repo",
|
||||
description:
|
||||
"The repo path on the provider. {namespace}/{repo_name}",
|
||||
},
|
||||
branch: {
|
||||
placeholder: "Enter branch",
|
||||
description: "Select a custom branch, or default to 'main'.",
|
||||
},
|
||||
commit: {
|
||||
placeholder: "Enter a specific commit hash. Optional.",
|
||||
description:
|
||||
"Switch to a specific hash after cloning the branch.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Run Path",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
run_directory: {
|
||||
placeholder: "./",
|
||||
description:
|
||||
"Set the cwd when running compose up command. Relative to the repo root.",
|
||||
boldLabel: true,
|
||||
{
|
||||
label: "Run Path",
|
||||
labelHidden: true,
|
||||
components: {
|
||||
run_directory: {
|
||||
placeholder: "./",
|
||||
description:
|
||||
"Set the cwd when running compose up command. Relative to the repo root.",
|
||||
boldLabel: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "File Paths",
|
||||
description:
|
||||
"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 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set((update) => ({
|
||||
...update,
|
||||
file_paths: [
|
||||
...(update.file_paths ?? config.file_paths ?? []),
|
||||
"",
|
||||
],
|
||||
}))
|
||||
}
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
Add File
|
||||
</Button>
|
||||
),
|
||||
components: {
|
||||
file_paths: (value, set) => (
|
||||
<InputList
|
||||
field="file_paths"
|
||||
values={value ?? []}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
placeholder="compose.yaml"
|
||||
/>
|
||||
{
|
||||
label: "File Paths",
|
||||
description:
|
||||
"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 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set((update) => ({
|
||||
...update,
|
||||
file_paths: [
|
||||
...(update.file_paths ?? config.file_paths ?? []),
|
||||
"",
|
||||
],
|
||||
}))
|
||||
}
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
Add File
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Git Webhooks",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Monitor",
|
||||
components: {
|
||||
["Guard" as any]: () => {
|
||||
if (update.branch ?? config.branch) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ConfigItem label="Configure Branch">
|
||||
<div>Must configure Branch before webhooks will work.</div>
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
["Refresh" as any]: () =>
|
||||
(update.branch ?? config.branch) && (
|
||||
<ConfigItem label="Refresh Cache">
|
||||
<CopyGithubWebhook path={`/stack/${id}/refresh`} />
|
||||
</ConfigItem>
|
||||
components: {
|
||||
file_paths: (value, set) => (
|
||||
<InputList
|
||||
field="file_paths"
|
||||
values={value ?? []}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
placeholder="compose.yaml"
|
||||
/>
|
||||
),
|
||||
["Deploy" as any]: () =>
|
||||
(update.branch ?? config.branch) && (
|
||||
<ConfigItem label="Auto Redeploy">
|
||||
<CopyGithubWebhook path={`/stack/${id}/deploy`} />
|
||||
</ConfigItem>
|
||||
),
|
||||
webhook_enabled:
|
||||
!!(update.branch ?? config.branch) &&
|
||||
webhooks !== undefined &&
|
||||
!webhooks.managed,
|
||||
webhook_secret: {
|
||||
description:
|
||||
"Provide a custom webhook secret for this resource, or use the global default.",
|
||||
placeholder: "Input custom secret",
|
||||
},
|
||||
["managed" as any]: () => {
|
||||
const inv = useInvalidate();
|
||||
const { toast } = useToast();
|
||||
const { mutate: createWebhook, isPending: createPending } =
|
||||
useWrite("CreateStackWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Created" });
|
||||
inv(["GetStackWebhooksEnabled", { stack: id }]);
|
||||
},
|
||||
});
|
||||
const { mutate: deleteWebhook, isPending: deletePending } =
|
||||
useWrite("DeleteStackWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Deleted" });
|
||||
inv(["GetStackWebhooksEnabled", { stack: id }]);
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!(update.branch ?? config.branch) ||
|
||||
!webhooks ||
|
||||
!webhooks.managed
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigItem label="Manage Webhook">
|
||||
{webhooks.deploy_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
and will trigger
|
||||
<div
|
||||
className={text_color_class_by_intention("Neutral")}
|
||||
>
|
||||
DEPLOY
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
deleteWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Deploy,
|
||||
})
|
||||
}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhooks.deploy_enabled && webhooks.refresh_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
and will trigger
|
||||
<div
|
||||
className={text_color_class_by_intention("Neutral")}
|
||||
>
|
||||
REFRESH
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
deleteWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Refresh,
|
||||
})
|
||||
}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhooks.deploy_enabled && !webhooks.refresh_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Critical"
|
||||
)}
|
||||
>
|
||||
DISABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Enable Deploy"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() =>
|
||||
createWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Deploy,
|
||||
})
|
||||
}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
<ConfirmButton
|
||||
title="Enable Refresh"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() =>
|
||||
createWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Refresh,
|
||||
})
|
||||
}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
label: "Git Webhooks",
|
||||
description:
|
||||
"Configure your repo provider to send webhooks to Monitor",
|
||||
components: {
|
||||
["Guard" as any]: () => {
|
||||
if (update.branch ?? config.branch) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ConfigItem label="Configure Branch">
|
||||
<div>
|
||||
Must configure Branch before webhooks will work.
|
||||
</div>
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
["Refresh" as any]: () =>
|
||||
(update.branch ?? config.branch) && (
|
||||
<ConfigItem label="Refresh Cache">
|
||||
<CopyGithubWebhook path={`/stack/${id}/refresh`} />
|
||||
</ConfigItem>
|
||||
),
|
||||
["Deploy" as any]: () =>
|
||||
(update.branch ?? config.branch) && (
|
||||
<ConfigItem label="Auto Redeploy">
|
||||
<CopyGithubWebhook path={`/stack/${id}/deploy`} />
|
||||
</ConfigItem>
|
||||
),
|
||||
webhook_enabled:
|
||||
!!(update.branch ?? config.branch) &&
|
||||
webhooks !== undefined &&
|
||||
!webhooks.managed,
|
||||
webhook_secret: {
|
||||
description:
|
||||
"Provide a custom webhook secret for this resource, or use the global default.",
|
||||
placeholder: "Input custom secret",
|
||||
},
|
||||
["managed" as any]: () => {
|
||||
const inv = useInvalidate();
|
||||
const { toast } = useToast();
|
||||
const { mutate: createWebhook, isPending: createPending } =
|
||||
useWrite("CreateStackWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Created" });
|
||||
inv(["GetStackWebhooksEnabled", { stack: id }]);
|
||||
},
|
||||
});
|
||||
const { mutate: deleteWebhook, isPending: deletePending } =
|
||||
useWrite("DeleteStackWebhook", {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Webhook Deleted" });
|
||||
inv(["GetStackWebhooksEnabled", { stack: id }]);
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!(update.branch ?? config.branch) ||
|
||||
!webhooks ||
|
||||
!webhooks.managed
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigItem label="Manage Webhook">
|
||||
{webhooks.deploy_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
and will trigger
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Neutral"
|
||||
)}
|
||||
>
|
||||
DEPLOY
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
deleteWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Deploy,
|
||||
})
|
||||
}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhooks.deploy_enabled && webhooks.refresh_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention("Good")}
|
||||
>
|
||||
ENABLED
|
||||
</div>
|
||||
and will trigger
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Neutral"
|
||||
)}
|
||||
>
|
||||
REFRESH
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Disable"
|
||||
icon={<Ban className="w-4 h-4" />}
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
deleteWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Refresh,
|
||||
})
|
||||
}
|
||||
loading={deletePending}
|
||||
disabled={disabled || deletePending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!webhooks.deploy_enabled &&
|
||||
!webhooks.refresh_enabled && (
|
||||
<div className="flex items-center gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
Incoming webhook is{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
"Critical"
|
||||
)}
|
||||
>
|
||||
DISABLED
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
title="Enable Deploy"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() =>
|
||||
createWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Deploy,
|
||||
})
|
||||
}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
<ConfirmButton
|
||||
title="Enable Refresh"
|
||||
icon={<CirclePlus className="w-4 h-4" />}
|
||||
onClick={() =>
|
||||
createWebhook({
|
||||
stack: id,
|
||||
action: Types.StackWebhookAction.Refresh,
|
||||
})
|
||||
}
|
||||
loading={createPending}
|
||||
disabled={disabled || createPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ConfigItem>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
environment: [
|
||||
{
|
||||
label: "Environment",
|
||||
|
||||
@@ -135,7 +135,7 @@ export const StackComponents: RequiredResourceComponents = {
|
||||
State: ({ id }) => {
|
||||
const state = useStack(id)?.info.state ?? Types.StackState.Unknown;
|
||||
const config = useFullStack(id)?.config;
|
||||
if (!config?.file_contents && !config?.repo) {
|
||||
if (!config?.files_on_host && !config?.file_contents && !config?.repo) {
|
||||
return null;
|
||||
}
|
||||
return <StatusBadge text={state} intent={stack_state_intention(state)} />;
|
||||
|
||||
@@ -10,6 +10,7 @@ If you have an idea for Monitor, feel free to open an issue beginning with the `
|
||||
- **v1.13**: Support "Compose" resource - Paste in a docker compose file and manage it like a Portainer "Stack" ✅
|
||||
- **v1.14**: Manage docker networks, images, volumes in the UI
|
||||
- **v1.15**: Support generic OAuth2 providers (including self-hosted).
|
||||
- **v1.16+**: Support "Cluster" resource - Manage Kubernetes cluster, can attach deployments to "Cluster" (in addition to existing "Server")
|
||||
- **v1.16**: Support "Swarm" resource - Manage docker swarms, attach Deployments / Stacks to "Swarm".
|
||||
- **v1.17+**: Support "Cluster" resource - Manage Kubernetes cluster, can attach deployments to "Cluster" (in addition to existing "Server")
|
||||
|
||||
**Note. The specific versions associated with these features are not final.**
|
||||
Reference in New Issue
Block a user