Compare commits

..

12 Commits

Author SHA1 Message Date
mbecker20
2463ed3879 1.13.4 Fix periphery image registry login / pull ordering 2024-08-20 12:50:07 -04:00
mbecker20
a2758ce6f4 Stack: login to registry BEFORE pulling image 2024-08-20 12:49:19 -04:00
mbecker20
3f1788dbbb docsite: reindent ports in example 2024-08-19 11:36:17 -04:00
mbecker20
33a0560af6 frontend: Ignore Services correct content hidden logic 2024-08-19 00:44:48 -04:00
mbecker20
610a10c488 frontend: show state when using Stack files-on-host 2024-08-18 21:16:53 -04:00
mbecker20
39b217687d 1.13.3 filter Periphery disks 2024-08-18 18:12:47 -04:00
mbecker20
2f73461979 stack don't show Git Repo config is file contents defined in UI 2024-08-18 18:06:12 -04:00
mbecker20
aae9bb9e51 move image registry to the top of Build Image config 2024-08-18 18:06:12 -04:00
mbecker20
7d011d93fa Fix: Periphery should flter out "overlay" volumes, not include them 2024-08-18 18:05:56 -04:00
mbecker20
bffdea4357 Swarm roadmap 2024-08-18 16:05:58 -04:00
mbecker20
790566bf79 add note about podman support 2024-08-18 13:37:26 -04:00
mbecker20
b17db93f13 quick-fix: fix build version config 2024-08-18 04:57:25 -04:00
10 changed files with 324 additions and 309 deletions

28
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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)

View File

@@ -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();

View File

@@ -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.

View File

@@ -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.

View File

@@ -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}
/>
),
}, },
}, },
{ {

View File

@@ -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",

View File

@@ -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)} />;

View File

@@ -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.**