WP-12: Kubernetes overview and workload actions
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled

kube-rs gateway (nodes, namespaces, deployments/statefulsets/daemonsets with
images, PVCs; rollout restart, scale, set image via patches) using the
microk8s kubeconfig by default, fake gateway for dev, /api/cluster routes,
Kubernetes page with node cards, workload table with admin actions and PVC
list. Also implements the streaming command runner that WP-11 relied on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-02 22:33:57 +02:00
parent 51ec216910
commit 8f36a54da0
15 changed files with 807 additions and 26 deletions

View File

@ -1,6 +1,8 @@
use std::sync::Arc;
use domain::cluster::{ClusterOverview, WorkloadRef};
use domain::cluster::{
validate_image, validate_k8s_name, ClusterOverview, WorkloadRef, MAX_REPLICAS,
};
use domain::ports::ClusterGateway;
use domain::DomainError;
@ -10,28 +12,55 @@ pub struct ClusterService {
impl ClusterService {
pub fn new(gateway: Arc<dyn ClusterGateway>) -> Self {
let _ = &gateway;
Self { gateway }
}
pub async fn overview(&self) -> Result<ClusterOverview, DomainError> {
todo!()
self.gateway.overview().await
}
pub async fn restart(&self, _w: WorkloadRef) -> Result<(), DomainError> {
todo!()
pub async fn restart(&self, w: WorkloadRef) -> Result<(), DomainError> {
validate_ref(&w)?;
self.gateway.restart(&w).await
}
pub async fn scale(&self, _w: WorkloadRef, _replicas: i32) -> Result<(), DomainError> {
todo!()
pub async fn scale(&self, w: WorkloadRef, replicas: i32) -> Result<(), DomainError> {
validate_ref(&w)?;
if !(0..=MAX_REPLICAS).contains(&replicas) {
return Err(DomainError::Validation(format!(
"replicas must be between 0 and {MAX_REPLICAS}"
)));
}
self.gateway.scale(&w, replicas).await
}
/// Change the image of `container` (default: the workload's first container).
pub async fn set_image(
&self,
_w: WorkloadRef,
_container: Option<String>,
_image: &str,
w: WorkloadRef,
container: Option<String>,
image: &str,
) -> Result<(), DomainError> {
todo!()
validate_ref(&w)?;
validate_image(image)?;
let container = match container {
Some(c) => c,
None => {
let overview = self.gateway.overview().await?;
overview
.workloads
.iter()
.find(|x| x.namespace == w.namespace && x.kind == w.kind && x.name == w.name)
.and_then(|x| x.containers.first())
.map(|c| c.name.clone())
.ok_or(DomainError::NotFound)?
}
};
self.gateway.set_image(&w, &container, image).await
}
}
fn validate_ref(w: &WorkloadRef) -> Result<(), DomainError> {
validate_k8s_name(&w.namespace)?;
validate_k8s_name(&w.name)
}