mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-29 12:43:26 -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]]
|
[[package]]
|
||||||
name = "alerter"
|
name = "alerter"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -922,7 +922,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "command"
|
name = "command"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"monitor_client",
|
"monitor_client",
|
||||||
"run_command",
|
"run_command",
|
||||||
@@ -1306,7 +1306,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "formatting"
|
name = "formatting"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serror",
|
"serror",
|
||||||
]
|
]
|
||||||
@@ -1437,7 +1437,7 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git"
|
name = "git"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"command",
|
"command",
|
||||||
@@ -2041,7 +2041,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "logger"
|
name = "logger"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"monitor_client",
|
"monitor_client",
|
||||||
@@ -2111,7 +2111,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "migrator"
|
name = "migrator"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2164,9 +2164,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mongo_indexed"
|
name = "mongo_indexed"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2570cf8b41b536db37f64a27bafb814e3f1e58db5266dd295427364d957e974f"
|
checksum = "556e2883109599e3cc28c7ad0d700c0b5c297e4d17ae810c669d18f79a02df26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mongo_indexed_derive",
|
"mongo_indexed_derive",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
@@ -2246,7 +2246,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monitor_cli"
|
name = "monitor_cli"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2262,7 +2262,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monitor_client"
|
name = "monitor_client"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async_timing_util",
|
"async_timing_util",
|
||||||
@@ -2294,7 +2294,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monitor_core"
|
name = "monitor_core"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async_timing_util",
|
"async_timing_util",
|
||||||
@@ -2349,7 +2349,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "monitor_periphery"
|
name = "monitor_periphery"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async_timing_util",
|
"async_timing_util",
|
||||||
@@ -2773,7 +2773,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "periphery_client"
|
name = "periphery_client"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"monitor_client",
|
"monitor_client",
|
||||||
@@ -4507,7 +4507,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "update_logger"
|
name = "update_logger"
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"logger",
|
"logger",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ resolver = "2"
|
|||||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.13.2"
|
version = "1.13.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
@@ -15,7 +15,7 @@ monitor_client = { path = "client/core/rs" }
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# LOCAL
|
# LOCAL
|
||||||
monitor_client = "1.13.2"
|
monitor_client = "1.13.3"
|
||||||
periphery_client = { path = "client/periphery/rs" }
|
periphery_client = { path = "client/periphery/rs" }
|
||||||
formatting = { path = "lib/formatting" }
|
formatting = { path = "lib/formatting" }
|
||||||
command = { path = "lib/command" }
|
command = { path = "lib/command" }
|
||||||
@@ -32,7 +32,7 @@ merge_config_files = "0.1.5"
|
|||||||
async_timing_util = "1.0.0"
|
async_timing_util = "1.0.0"
|
||||||
partial_derive2 = "0.4.3"
|
partial_derive2 = "0.4.3"
|
||||||
derive_variants = "1.0.0"
|
derive_variants = "1.0.0"
|
||||||
mongo_indexed = "2.0.0"
|
mongo_indexed = "2.0.1"
|
||||||
resolver_api = "1.1.1"
|
resolver_api = "1.1.1"
|
||||||
toml_pretty = "1.1.2"
|
toml_pretty = "1.1.2"
|
||||||
mungos = "1.0.1"
|
mungos = "1.0.1"
|
||||||
|
|||||||
@@ -122,22 +122,6 @@ pub async fn compose_up(
|
|||||||
let last_project_name = stack.project_name(false);
|
let last_project_name = stack.project_name(false);
|
||||||
let project_name = stack.project_name(true);
|
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
|
// Login to the registry to pull private images, if account is set
|
||||||
if !stack.config.registry_account.is_empty() {
|
if !stack.config.registry_account.is_empty() {
|
||||||
let registry = ImageRegistry::Standard(StandardRegistryConfig {
|
let registry = ImageRegistry::Standard(StandardRegistryConfig {
|
||||||
@@ -157,6 +141,22 @@ pub async fn compose_up(
|
|||||||
.context("failed to login to image registry")?;
|
.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.
|
// Take down the existing containers.
|
||||||
// This one tries to use the previously deployed service name, to ensure the right stack is taken down.
|
// 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)
|
destroy_existing_containers(&last_project_name, service, res)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl StatsClient {
|
|||||||
.list()
|
.list()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| {
|
.filter(|d| {
|
||||||
if d.file_system() != "overlay" {
|
if d.file_system() == "overlay" {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let path = d.mount_point();
|
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
|
image: ghcr.io/mbecker20/periphery:latest # use ghcr.io/mbecker20/periphery:latest-aarch64 for arm support
|
||||||
logging:
|
logging:
|
||||||
driver: local
|
driver: local
|
||||||
ports:
|
ports:
|
||||||
- 8120:8120
|
- 8120:8120
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /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.
|
- 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.
|
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
|
## Architecture and Components
|
||||||
|
|
||||||
Monitor is composed of a single core and any amount of connected servers running the periphery application.
|
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) => {
|
version: (_version, set) => {
|
||||||
const version =
|
const version =
|
||||||
typeof _version === "object"
|
typeof _version === "object"
|
||||||
? `${_version.major}.${_version.major}.${_version.patch}`
|
? `${_version.major}.${_version.minor}.${_version.patch}`
|
||||||
: _version;
|
: _version;
|
||||||
return (
|
return (
|
||||||
<ConfigInput
|
<ConfigInput
|
||||||
@@ -136,6 +136,14 @@ export const BuildConfig = ({
|
|||||||
{
|
{
|
||||||
label: "Image",
|
label: "Image",
|
||||||
components: {
|
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: {
|
build_path: {
|
||||||
placeholder: ".",
|
placeholder: ".",
|
||||||
description:
|
description:
|
||||||
@@ -146,14 +154,6 @@ export const BuildConfig = ({
|
|||||||
description:
|
description:
|
||||||
"The path to the dockerfile, relative to the build path.",
|
"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;
|
if (!config) return null;
|
||||||
|
|
||||||
const disabled = global_disabled || perms !== Types.PermissionLevel.Write;
|
const disabled = global_disabled || perms !== Types.PermissionLevel.Write;
|
||||||
|
|
||||||
const files_on_host = update.files_on_host ?? config.files_on_host;
|
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 (
|
return (
|
||||||
<Config
|
<Config
|
||||||
@@ -283,7 +284,7 @@ export const StackConfig = ({
|
|||||||
description:
|
description:
|
||||||
"If your compose file has init services that exit early, ignore them here so your stack will report the correct health.",
|
"If your compose file has init services that exit early, ignore them here so your stack will report the correct health.",
|
||||||
contentHidden:
|
contentHidden:
|
||||||
((update.extra_args ?? config.extra_args)?.length ?? 0) === 0,
|
((update.ignore_services ?? config.ignore_services)?.length ?? 0) === 0,
|
||||||
actions: !disabled && (
|
actions: !disabled && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -317,274 +318,282 @@ export const StackConfig = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"Git Repo": !files_on_host && [
|
"Git Repo": !files_on_host &&
|
||||||
{
|
!ui_file_contents && [
|
||||||
label: "Git",
|
{
|
||||||
description:
|
label: "Git",
|
||||||
"Provide config for repo-based compose files. Not necessary if file contents are configured in UI.",
|
description:
|
||||||
components: {
|
"Provide config for repo-based compose files. Not necessary if file contents are configured in UI.",
|
||||||
git_provider: (provider, set) => {
|
components: {
|
||||||
const https = update.git_https ?? config.git_https;
|
git_provider: (provider, set) => {
|
||||||
return (
|
const https = update.git_https ?? config.git_https;
|
||||||
<ProviderSelectorConfig
|
return (
|
||||||
account_type="git"
|
<ProviderSelectorConfig
|
||||||
selected={provider}
|
account_type="git"
|
||||||
disabled={disabled}
|
selected={provider}
|
||||||
onSelect={(git_provider) => set({ git_provider })}
|
disabled={disabled}
|
||||||
https={https}
|
onSelect={(git_provider) => set({ git_provider })}
|
||||||
onHttpsSwitch={() => set({ git_https: !https })}
|
https={https}
|
||||||
/>
|
onHttpsSwitch={() => set({ git_https: !https })}
|
||||||
);
|
/>
|
||||||
},
|
);
|
||||||
git_account: (value, set) => {
|
},
|
||||||
const server_id = update.server_id || config.server_id;
|
git_account: (value, set) => {
|
||||||
return (
|
const server_id = update.server_id || config.server_id;
|
||||||
<AccountSelectorConfig
|
return (
|
||||||
id={server_id}
|
<AccountSelectorConfig
|
||||||
type={server_id ? "Server" : "None"}
|
id={server_id}
|
||||||
account_type="git"
|
type={server_id ? "Server" : "None"}
|
||||||
provider={update.git_provider ?? config.git_provider}
|
account_type="git"
|
||||||
selected={value}
|
provider={update.git_provider ?? config.git_provider}
|
||||||
onSelect={(git_account) => set({ git_account })}
|
selected={value}
|
||||||
disabled={disabled}
|
onSelect={(git_account) => set({ git_account })}
|
||||||
placeholder="None"
|
disabled={disabled}
|
||||||
/>
|
placeholder="None"
|
||||||
);
|
/>
|
||||||
},
|
);
|
||||||
repo: {
|
},
|
||||||
placeholder: "Enter repo",
|
repo: {
|
||||||
description:
|
placeholder: "Enter repo",
|
||||||
"The repo path on the provider. {namespace}/{repo_name}",
|
description:
|
||||||
},
|
"The repo path on the provider. {namespace}/{repo_name}",
|
||||||
branch: {
|
},
|
||||||
placeholder: "Enter branch",
|
branch: {
|
||||||
description: "Select a custom branch, or default to 'main'.",
|
placeholder: "Enter branch",
|
||||||
},
|
description: "Select a custom branch, or default to 'main'.",
|
||||||
commit: {
|
},
|
||||||
placeholder: "Enter a specific commit hash. Optional.",
|
commit: {
|
||||||
description:
|
placeholder: "Enter a specific commit hash. Optional.",
|
||||||
"Switch to a specific hash after cloning the branch.",
|
description:
|
||||||
|
"Switch to a specific hash after cloning the branch.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Run Path",
|
||||||
label: "Run Path",
|
labelHidden: true,
|
||||||
labelHidden: true,
|
components: {
|
||||||
components: {
|
run_directory: {
|
||||||
run_directory: {
|
placeholder: "./",
|
||||||
placeholder: "./",
|
description:
|
||||||
description:
|
"Set the cwd when running compose up command. Relative to the repo root.",
|
||||||
"Set the cwd when running compose up command. Relative to the repo root.",
|
boldLabel: true,
|
||||||
boldLabel: true,
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "File Paths",
|
||||||
label: "File Paths",
|
description:
|
||||||
description:
|
"Add files to include using 'docker compose -f'. If empty, uses 'compose.yaml'. Relative to 'Run Directory'.",
|
||||||
"Add files to include using 'docker compose -f'. If empty, uses 'compose.yaml'. Relative to 'Run Directory'.",
|
contentHidden:
|
||||||
contentHidden:
|
(update.file_paths ?? config.file_paths)?.length === 0,
|
||||||
(update.file_paths ?? config.file_paths)?.length === 0,
|
actions: !disabled && (
|
||||||
actions: !disabled && (
|
<Button
|
||||||
<Button
|
variant="secondary"
|
||||||
variant="secondary"
|
onClick={() =>
|
||||||
onClick={() =>
|
set((update) => ({
|
||||||
set((update) => ({
|
...update,
|
||||||
...update,
|
file_paths: [
|
||||||
file_paths: [
|
...(update.file_paths ?? config.file_paths ?? []),
|
||||||
...(update.file_paths ?? config.file_paths ?? []),
|
"",
|
||||||
"",
|
],
|
||||||
],
|
}))
|
||||||
}))
|
}
|
||||||
}
|
className="flex items-center gap-2 w-[200px]"
|
||||||
className="flex items-center gap-2 w-[200px]"
|
>
|
||||||
>
|
<PlusCircle className="w-4 h-4" />
|
||||||
<PlusCircle className="w-4 h-4" />
|
Add File
|
||||||
Add File
|
</Button>
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
components: {
|
|
||||||
file_paths: (value, set) => (
|
|
||||||
<InputList
|
|
||||||
field="file_paths"
|
|
||||||
values={value ?? []}
|
|
||||||
set={set}
|
|
||||||
disabled={disabled}
|
|
||||||
placeholder="compose.yaml"
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
},
|
components: {
|
||||||
},
|
file_paths: (value, set) => (
|
||||||
{
|
<InputList
|
||||||
label: "Git Webhooks",
|
field="file_paths"
|
||||||
description:
|
values={value ?? []}
|
||||||
"Configure your repo provider to send webhooks to Monitor",
|
set={set}
|
||||||
components: {
|
disabled={disabled}
|
||||||
["Guard" as any]: () => {
|
placeholder="compose.yaml"
|
||||||
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>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
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: [
|
environment: [
|
||||||
{
|
{
|
||||||
label: "Environment",
|
label: "Environment",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const StackComponents: RequiredResourceComponents = {
|
|||||||
State: ({ id }) => {
|
State: ({ id }) => {
|
||||||
const state = useStack(id)?.info.state ?? Types.StackState.Unknown;
|
const state = useStack(id)?.info.state ?? Types.StackState.Unknown;
|
||||||
const config = useFullStack(id)?.config;
|
const config = useFullStack(id)?.config;
|
||||||
if (!config?.file_contents && !config?.repo) {
|
if (!config?.files_on_host && !config?.file_contents && !config?.repo) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <StatusBadge text={state} intent={stack_state_intention(state)} />;
|
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.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.14**: Manage docker networks, images, volumes in the UI
|
||||||
- **v1.15**: Support generic OAuth2 providers (including self-hosted).
|
- **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.**
|
**Note. The specific versions associated with these features are not final.**
|
||||||
Reference in New Issue
Block a user