diff --git a/bin/periphery/src/api/mod.rs b/bin/periphery/src/api/mod.rs index 1ba8d807a..7c8e89bff 100644 --- a/bin/periphery/src/api/mod.rs +++ b/bin/periphery/src/api/mod.rs @@ -3,14 +3,15 @@ use async_trait::async_trait; use monitor_client::entities::{update::Log, SystemCommand}; use periphery_client::api::{ build::*, container::*, git::*, network::*, stats::*, GetAccounts, - GetHealth, GetSecrets, GetVersion, GetVersionResponse, RunCommand, + GetHealth, GetSecrets, GetVersion, GetVersionResponse, PruneAll, + RunCommand, }; use resolver_api::{derive::Resolver, Resolve, ResolveToString}; use serde::{Deserialize, Serialize}; use crate::{ config::{accounts_response, secrets_response}, - helpers::run_monitor_command, + helpers::{docker, run_monitor_command}, State, }; @@ -70,6 +71,7 @@ pub enum PeripheryRequest { CreateNetwork(CreateNetwork), DeleteNetwork(DeleteNetwork), PruneNetworks(PruneNetworks), + PruneAll(PruneAll), } // @@ -147,3 +149,22 @@ impl Resolve for State { .context("failure in spawned task") } } + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + PruneAll {}: PruneAll, + _: (), + ) -> anyhow::Result> { + tokio::spawn(async move { + let mut logs = Vec::new(); + logs.push(docker::prune_images().await); + logs.push(docker::container::prune_containers().await); + logs.push(docker::network::prune_networks().await); + logs + }) + .await + .context("failure in spawned task") + } +} diff --git a/client/core/rs/src/api/read/server.rs b/client/core/rs/src/api/read/server.rs index cd1debb0b..203508de2 100644 --- a/client/core/rs/src/api/read/server.rs +++ b/client/core/rs/src/api/read/server.rs @@ -113,6 +113,57 @@ pub struct GetPeripheryVersionResponse { // +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorReadRequest)] +#[response(GetDockerNetworksResponse)] +pub struct GetDockerNetworks { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub server: String, +} + +#[typeshare] +pub type GetDockerNetworksResponse = Vec; + +// + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorReadRequest)] +#[response(GetDockerImagesResponse)] +pub struct GetDockerImages { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub server: String, +} + +#[typeshare] +pub type GetDockerImagesResponse = Vec; + +// + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorReadRequest)] +#[response(GetDockerContainersResponse)] +pub struct GetDockerContainers { + /// Id or name + #[serde(alias = "id", alias = "name")] + pub server: String, +} + +#[typeshare] +pub type GetDockerContainersResponse = Vec; + +// + #[typeshare] #[derive( Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, @@ -249,23 +300,6 @@ pub type GetSystemComponentsResponse = Vec; // -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, -)] -#[empty_traits(MonitorReadRequest)] -#[response(GetDockerNetworksResponse)] -pub struct GetDockerNetworks { - /// Id or name - #[serde(alias = "id", alias = "name")] - pub server: String, -} - -#[typeshare] -pub type GetDockerNetworksResponse = Vec; - -// - #[typeshare] #[derive( Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, @@ -290,40 +324,6 @@ pub struct GetHistoricalServerStatsResponse { // -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, -)] -#[empty_traits(MonitorReadRequest)] -#[response(GetDockerImagesResponse)] -pub struct GetDockerImages { - /// Id or name - #[serde(alias = "id", alias = "name")] - pub server: String, -} - -#[typeshare] -pub type GetDockerImagesResponse = Vec; - -// - -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, -)] -#[empty_traits(MonitorReadRequest)] -#[response(GetDockerContainersResponse)] -pub struct GetDockerContainers { - /// Id or name - #[serde(alias = "id", alias = "name")] - pub server: String, -} - -#[typeshare] -pub type GetDockerContainersResponse = Vec; - -// - #[typeshare] #[derive( Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, diff --git a/client/periphery/rs/src/api/mod.rs b/client/periphery/rs/src/api/mod.rs index a7d8f332d..fd92fcf04 100644 --- a/client/periphery/rs/src/api/mod.rs +++ b/client/periphery/rs/src/api/mod.rs @@ -48,6 +48,12 @@ pub struct GetSecrets {} // +#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[response(Vec)] +pub struct PruneAll {} + +// + #[derive(Serialize, Deserialize, Debug, Clone, Request)] #[response(Log)] pub struct RunCommand { diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index e780a316c..091caeb23 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -48,56 +48,58 @@ export const BuildComponents: RequiredResourceComponents = { if (id) return ; else return ; }, - Actions: ({ id }) => { - const { toast } = useToast(); - const building = useRead("GetBuildActionState", { build: id }).data - ?.building; - const { mutate: run_mutate, isPending: runPending } = useExecute( - "RunBuild", - { - onMutate: () => { - toast({ title: "Run Build Sent" }); - }, - } - ); - const { mutate: cancel_mutate, isPending: cancelPending } = useExecute( - "CancelBuild", - { - onMutate: () => { - toast({ title: "Cancel Build Sent" }); - }, - onSuccess: () => { - toast({ title: "Build Cancelled" }); - }, - } - ); - if (building) { - return ( - } - onClick={() => cancel_mutate({ build: id })} - disabled={cancelPending} - /> + Actions: [ + ({ id }) => { + const { toast } = useToast(); + const building = useRead("GetBuildActionState", { build: id }).data + ?.building; + const { mutate: run_mutate, isPending: runPending } = useExecute( + "RunBuild", + { + onMutate: () => { + toast({ title: "Run Build Sent" }); + }, + } ); - } else { - return ( - - ) : ( - - ) - } - onClick={() => run_mutate({ build: id })} - disabled={runPending} - /> + const { mutate: cancel_mutate, isPending: cancelPending } = useExecute( + "CancelBuild", + { + onMutate: () => { + toast({ title: "Cancel Build Sent" }); + }, + onSuccess: () => { + toast({ title: "Build Cancelled" }); + }, + } ); - } - }, + if (building) { + return ( + } + onClick={() => cancel_mutate({ build: id })} + disabled={cancelPending} + /> + ); + } else { + return ( + + ) : ( + + ) + } + onClick={() => run_mutate({ build: id })} + disabled={runPending} + /> + ); + } + }, + ], New: () => { const { mutateAsync } = useWrite("CreateBuild"); const [name, setName] = useState(""); diff --git a/frontend/src/components/resources/deployment/actions.tsx b/frontend/src/components/resources/deployment/actions.tsx index 2e54b1c6a..e8317c2e4 100644 --- a/frontend/src/components/resources/deployment/actions.tsx +++ b/frontend/src/components/resources/deployment/actions.tsx @@ -3,7 +3,7 @@ import { ActionWithDialog, ConfirmButton, } from "@components/util"; -import { Play, Trash, Pause, Rocket, Pen } from "lucide-react"; +import { Play, Trash, Pause, Rocket, Pen, Loader2 } from "lucide-react"; import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks"; import { useNavigate } from "react-router-dom"; import { Input } from "@ui/input"; @@ -21,14 +21,20 @@ export const RedeployContainer = ({ id }: DeploymentId) => { const deployment = deployments?.find((d) => d.id === id); const deploying = useRead("GetDeploymentActionState", { deployment: id }).data ?.deploying; - + const pending = isPending || deploying; return ( } + icon={ + pending ? ( + + ) : ( + + ) + } onClick={() => mutate({ deployment: id })} - disabled={isPending} - loading={deploying} + disabled={pending} + loading={pending} /> ); }; diff --git a/frontend/src/components/resources/server/actions.tsx b/frontend/src/components/resources/server/actions.tsx index a06a9467d..623ea799d 100644 --- a/frontend/src/components/resources/server/actions.tsx +++ b/frontend/src/components/resources/server/actions.tsx @@ -1,11 +1,48 @@ -import { ActionButton, ActionWithDialog } from "@components/util"; -import { useInvalidate, useRead, useWrite } from "@lib/hooks"; +import { + ActionButton, + ActionWithDialog, + ConfirmButton, +} from "@components/util"; +import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks"; +import { Types } from "@monitor/client"; +import { IdComponent } from "@types"; import { Input } from "@ui/input"; import { useToast } from "@ui/use-toast"; -import { Pen, Trash } from "lucide-react"; +import { Loader2, Pen, Scissors, Trash } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; +type PruneType = "Images" | "Containers" | "Networks"; + +const PruneButton = ({ type, id }: { type: PruneType; id: string }) => { + const { mutate, isPending } = useExecute(`Prune${type}`); + const pruning = useRead("GetServerActionState", { server: id }).data?.[ + `pruning_${type.toLowerCase()}` as keyof Types.ServerActionState + ]; + const pending = isPending || pruning; + return ( + + ) : ( + + ) + } + onClick={() => mutate({ server: id })} + /> + ); +}; + +const PRUNE_TYPES: PruneType[] = ["Images", "Containers"]; + +export const SERVER_ACTIONS: IdComponent[] = PRUNE_TYPES.map( + (type) => + ({ id }) => + +); + export const DeleteServer = ({ id }: { id: string }) => { const nav = useNavigate(); const server = useRead("GetServer", { server: id }).data; diff --git a/frontend/src/components/resources/server/config.tsx b/frontend/src/components/resources/server/config.tsx index 272129c32..a5a125949 100644 --- a/frontend/src/components/resources/server/config.tsx +++ b/frontend/src/components/resources/server/config.tsx @@ -28,6 +28,7 @@ export const ServerConfig = ({ id }: { id: string }) => { region: true, enabled: true, auto_prune: true, + send_unreachable_alerts: true, }, }, warnings: { diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 051c1d547..521f0430f 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -7,7 +7,7 @@ import { ServerStats } from "./stats"; import { useState } from "react"; import { NewResource, Section } from "@components/layouts"; import { Input } from "@ui/input"; -import { DeleteServer, RenameServer } from "./actions"; +import { DeleteServer, RenameServer, SERVER_ACTIONS } from "./actions"; import { fill_color_class_by_intention, server_status_intention, @@ -50,7 +50,7 @@ export const ServerComponents: RequiredResourceComponents = { ); }, - Actions: () => null, + Actions: SERVER_ACTIONS, Page: { Stats: ({ id }) => , Deployments: ({ id }) => { diff --git a/frontend/src/pages/resource.tsx b/frontend/src/pages/resource.tsx index 7c67c522c..4be9fc0f1 100644 --- a/frontend/src/pages/resource.tsx +++ b/frontend/src/pages/resource.tsx @@ -38,7 +38,13 @@ export const Resource = () => { } - actions={} + actions={ +
+ {Components.Actions.map((Action) => ( + + ))} +
+ } > {/* */} diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index 158597a80..c6b6f3c3b 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -16,10 +16,11 @@ export interface RequiredResourceComponents { Name: IdComponent; Description: IdComponent; - Info: IdComponent[]; Status: IdComponent; Link: IdComponent; - Actions: IdComponent; + + Info: IdComponent[]; + Actions: IdComponent[]; Table: React.FC;