Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions crates/aetheris-app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use std::{

use aetheris_kube::{
AddClusterRequest, ClusterSummary, ContainerResources, ContainerUsage, ContextInfo,
KubeManager, ObjectCondition, ObjectDetail, ObjectEvent, ObjectSummary, ObjectWatchEvent,
PodExecEvent, PodExecRequest, PodLogRequest, PodPortForwardEvent, PodPortForwardRequest,
ResourceKind, ResourceUsage,
IngressRule, KubeManager, ObjectCondition, ObjectDetail, ObjectEvent, ObjectSummary,
ObjectWatchEvent, PodExecEvent, PodExecRequest, PodLogRequest, PodPortForwardEvent,
PodPortForwardRequest, ResourceKind, ResourceUsage, ServicePort,
};
use futures::FutureExt;
use futures::future::{AbortHandle, Abortable};
Expand Down Expand Up @@ -90,6 +90,9 @@ pub(super) struct DetailPane {
yaml_buffer: sourceview5::Buffer,
events_list: gtk::ListBox,
conditions_list: gtk::ListBox,
service_ports_list: gtk::ListBox,
service_selectors_list: gtk::ListBox,
ingress_rules_list: gtk::ListBox,
related_pods_store: gtk::gio::ListStore,
related_pods_sorted: gtk::SortListModel,
related_pod_states_section: gtk::Box,
Expand Down
15 changes: 15 additions & 0 deletions crates/aetheris-app/src/app/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,15 @@ impl Component for App {
let detail_conditions_list = gtk::ListBox::new();
detail_conditions_list.add_css_class("boxed-list");
detail_conditions_list.set_selection_mode(gtk::SelectionMode::None);
let detail_service_ports_list = gtk::ListBox::new();
detail_service_ports_list.add_css_class("boxed-list");
detail_service_ports_list.set_selection_mode(gtk::SelectionMode::None);
let detail_service_selectors_list = gtk::ListBox::new();
detail_service_selectors_list.add_css_class("boxed-list");
detail_service_selectors_list.set_selection_mode(gtk::SelectionMode::None);
let detail_ingress_rules_list = gtk::ListBox::new();
detail_ingress_rules_list.add_css_class("boxed-list");
detail_ingress_rules_list.set_selection_mode(gtk::SelectionMode::None);
let (detail_related_pods_view, detail_related_pods_store, detail_related_pods_sorted) =
super::widgets::related_pods_column_view();
let detail_related_pod_states = gtk::FlowBox::builder()
Expand Down Expand Up @@ -489,6 +498,9 @@ impl Component for App {
yaml_error_label: &detail_yaml_error_label,
events_list: &detail_events_list,
conditions_list: &detail_conditions_list,
service_ports_list: &detail_service_ports_list,
service_selectors_list: &detail_service_selectors_list,
ingress_rules_list: &detail_ingress_rules_list,
related_pods_view: &detail_related_pods_view,
related_pod_states_section: &detail_related_pod_states_section,
related_pod_states: &detail_related_pod_states,
Expand Down Expand Up @@ -982,6 +994,9 @@ impl Component for App {
yaml_buffer: detail_yaml_buffer,
events_list: detail_events_list,
conditions_list: detail_conditions_list,
service_ports_list: detail_service_ports_list,
service_selectors_list: detail_service_selectors_list,
ingress_rules_list: detail_ingress_rules_list,
related_pods_store: detail_related_pods_store,
related_pods_sorted: detail_related_pods_sorted,
related_pod_states_section: detail_related_pod_states_section,
Expand Down
32 changes: 32 additions & 0 deletions crates/aetheris-app/src/app/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,9 @@ impl App {
}
rebuild_detail_events(&self.detail.events_list, detail);
rebuild_detail_conditions(&self.detail.conditions_list, detail);
rebuild_service_ports(&self.detail.service_ports_list, detail);
rebuild_service_selectors(&self.detail.service_selectors_list, detail);
rebuild_ingress_rules(&self.detail.ingress_rules_list, detail);
rebuild_related_pods(
&self.detail.related_pods_store,
&self.detail.related_pods_stack,
Expand All @@ -949,6 +952,8 @@ impl App {
let show_pods = detail.kind == "Deployment";
let show_conditions = !detail.conditions.is_empty();
let show_containers = detail.kind == "Pod";
let show_service_tabs = detail.kind == "Service";
let show_ingress_rules = detail.kind == "Ingress";

set_stack_page(
&self.detail.stack,
Expand Down Expand Up @@ -977,6 +982,33 @@ impl App {
&[("{count}", detail.containers.len().to_string())],
),
);
set_stack_page(
&self.detail.stack,
"service-ports",
show_service_tabs,
&tr_format(
"Ports ({count})",
&[("{count}", detail.service_ports.len().to_string())],
),
);
set_stack_page(
&self.detail.stack,
"service-selectors",
show_service_tabs,
&tr_format(
"Selectors ({count})",
&[("{count}", detail.service_selectors.len().to_string())],
),
);
set_stack_page(
&self.detail.stack,
"ingress-rules",
show_ingress_rules,
&tr_format(
"Rules ({count})",
&[("{count}", detail.ingress_rules.len().to_string())],
),
);
set_stack_page(
&self.detail.stack,
"events",
Expand Down
155 changes: 155 additions & 0 deletions crates/aetheris-app/src/app/object_detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub(super) struct ObjectDetailWidgets<'a> {
pub(super) yaml_error_label: &'a gtk::Label,
pub(super) events_list: &'a gtk::ListBox,
pub(super) conditions_list: &'a gtk::ListBox,
pub(super) service_ports_list: &'a gtk::ListBox,
pub(super) service_selectors_list: &'a gtk::ListBox,
pub(super) ingress_rules_list: &'a gtk::ListBox,
pub(super) related_pods_view: &'a gtk::ColumnView,
pub(super) related_pod_states_section: &'a gtk::Box,
pub(super) related_pod_states: &'a gtk::FlowBox,
Expand Down Expand Up @@ -177,6 +180,44 @@ pub(super) fn build_object_detail_page(widgets: ObjectDetailWidgets<'_>) -> gtk:
.stack
.add_titled(&containers_page, Some("containers"), &tr("Containers"));

let service_ports_page = gtk::Box::new(gtk::Orientation::Vertical, 0);
service_ports_page.set_margin_all(12);
let service_ports_scrolled = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build();
service_ports_scrolled.set_child(Some(widgets.service_ports_list));
service_ports_page.append(&service_ports_scrolled);
widgets
.stack
.add_titled(&service_ports_page, Some("service-ports"), &tr("Ports"));

let service_selectors_page = gtk::Box::new(gtk::Orientation::Vertical, 0);
service_selectors_page.set_margin_all(12);
let service_selectors_scrolled = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build();
service_selectors_scrolled.set_child(Some(widgets.service_selectors_list));
service_selectors_page.append(&service_selectors_scrolled);
widgets.stack.add_titled(
&service_selectors_page,
Some("service-selectors"),
&tr("Selectors"),
);

let ingress_rules_page = gtk::Box::new(gtk::Orientation::Vertical, 0);
ingress_rules_page.set_margin_all(12);
let ingress_rules_scrolled = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build();
ingress_rules_scrolled.set_child(Some(widgets.ingress_rules_list));
ingress_rules_page.append(&ingress_rules_scrolled);
widgets
.stack
.add_titled(&ingress_rules_page, Some("ingress-rules"), &tr("Rules"));

let pods_page = gtk::Box::new(gtk::Orientation::Vertical, 8);
pods_page.set_margin_all(12);
let pods_scrolled = gtk::ScrolledWindow::builder()
Expand Down Expand Up @@ -357,6 +398,120 @@ pub(super) fn rebuild_detail_conditions(list: &gtk::ListBox, detail: &ObjectDeta
}
}

pub(super) fn rebuild_service_ports(list: &gtk::ListBox, detail: &ObjectDetail) {
while let Some(child) = list.first_child() {
list.remove(&child);
}

if detail.service_ports.is_empty() {
list.append(&detail_message_row(
&tr("No ports"),
&tr("This Service does not expose any ports."),
"dialog-information-symbolic",
));
return;
}

for port in &detail.service_ports {
list.append(&service_port_row(port));
}
}

pub(super) fn rebuild_service_selectors(list: &gtk::ListBox, detail: &ObjectDetail) {
while let Some(child) = list.first_child() {
list.remove(&child);
}

if detail.service_selectors.is_empty() {
list.append(&detail_message_row(
&tr("No selectors"),
&tr("This Service does not select Pods."),
"dialog-information-symbolic",
));
return;
}

for selector in &detail.service_selectors {
let row = adw::ActionRow::builder()
.title(&selector.key)
.subtitle(&selector.value)
.build();
row.add_prefix(&gtk::Image::from_icon_name("lucide-waypoints-symbolic"));
list.append(&row);
}
}

pub(super) fn rebuild_ingress_rules(list: &gtk::ListBox, detail: &ObjectDetail) {
while let Some(child) = list.first_child() {
list.remove(&child);
}

if detail.ingress_rules.is_empty() {
list.append(&detail_message_row(
&tr("No rules"),
&tr("This Ingress does not route traffic to a Service."),
"dialog-information-symbolic",
));
return;
}

for rule in &detail.ingress_rules {
list.append(&ingress_rule_row(rule));
}
}

fn ingress_rule_row(rule: &IngressRule) -> adw::ActionRow {
let target = tr_format(
"{service}:{port}",
&[
("{service}", rule.service.clone()),
("{port}", rule.port.clone()),
],
);
let subtitle = tr_format(
"{path_type} → {target}",
&[
("{path_type}", rule.path_type.clone()),
("{target}", target),
],
);
let row = adw::ActionRow::builder()
.title(format!("{}{}", rule.host, rule.path))
.subtitle(subtitle)
.build();
row.add_prefix(&gtk::Image::from_icon_name("lucide-radio-tower-symbolic"));
row
}

fn service_port_row(port: &ServicePort) -> adw::ActionRow {
let title = tr_format(
"Port {port} → Target {target}",
&[
("{port}", port.port.clone()),
("{target}", port.target_port.clone()),
],
);
let mut details = vec![port.protocol.clone()];
if !port.name.is_empty() {
details.push(port.name.clone());
}
if let Some(node_port) = &port.node_port {
details.push(tr_format(
"NodePort {port}",
&[("{port}", node_port.clone())],
));
}

let row = adw::ActionRow::builder()
.title(title)
.subtitle(details.join(" · "))
.build();
row.add_prefix(&gtk::Image::from_icon_name(
"network-transmit-receive-symbolic",
));
row
}

pub(super) fn rebuild_related_pods(
store: &gtk::gio::ListStore,
stack: &gtk::Stack,
Expand Down
23 changes: 21 additions & 2 deletions crates/aetheris-app/src/app/projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ pub(super) enum StatusFilter {
pub(crate) enum ObjectColumn {
Image,
Namespace,
Target,
Selector,
IngressClass,
Status,
Cpu,
Memory,
Expand Down Expand Up @@ -158,9 +161,12 @@ impl ObjectFavorite {
}

impl ObjectColumn {
pub(super) const ALL: [Self; 7] = [
pub(super) const ALL: [Self; 10] = [
Self::Image,
Self::Namespace,
Self::Target,
Self::Selector,
Self::IngressClass,
Self::Status,
Self::Cpu,
Self::Memory,
Expand All @@ -172,6 +178,9 @@ impl ObjectColumn {
match self {
Self::Image => tr("Image"),
Self::Namespace => tr("Namespace"),
Self::Target => tr("Target"),
Self::Selector => tr("Selector"),
Self::IngressClass => tr("Ingress Class"),
Self::Status => tr("Status"),
Self::Cpu => tr("CPU"),
Self::Memory => tr("Memory"),
Expand All @@ -184,6 +193,9 @@ impl ObjectColumn {
match self {
Self::Image => OBJECT_IMAGE_WIDTH,
Self::Namespace => OBJECT_NAMESPACE_WIDTH,
Self::Target => 156,
Self::Selector => 180,
Self::IngressClass => 144,
Self::Status => OBJECT_STATUS_WIDTH,
Self::Cpu | Self::Memory => OBJECT_METRIC_WIDTH,
Self::Api => OBJECT_API_WIDTH,
Expand All @@ -196,7 +208,7 @@ pub(super) fn default_object_columns() -> Vec<ObjectColumn> {
ObjectColumn::ALL.to_vec()
}

const OBJECT_COLUMN_SCHEMA_VERSION: u32 = 1;
const OBJECT_COLUMN_SCHEMA_VERSION: u32 = 3;

impl StatusFilter {
pub(super) const ALL: [Self; 6] = [
Expand Down Expand Up @@ -553,6 +565,13 @@ impl ProjectStore {
{
self.visible_object_columns.push(ObjectColumn::Image);
}
if self.object_column_schema_version < 2 {
self.visible_object_columns
.extend([ObjectColumn::Target, ObjectColumn::Selector]);
}
if self.object_column_schema_version < 3 {
self.visible_object_columns.push(ObjectColumn::IngressClass);
}
self.object_column_schema_version = OBJECT_COLUMN_SCHEMA_VERSION;
}

Expand Down
Loading
Loading