From bac3b0e159c276ae2a9d8df7138f44be72cbdf87 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 24 Sep 2024 10:08:28 +0200 Subject: [PATCH] feat: Add identity.projects view --- openstack_tui/src/action.rs | 8 ++ openstack_tui/src/app.rs | 8 +- openstack_tui/src/cloud_services.rs | 9 +- openstack_tui/src/cloud_services/identity.rs | 20 ++- openstack_tui/src/cloud_worker.rs | 11 ++ openstack_tui/src/components.rs | 1 + openstack_tui/src/components/identity.rs | 15 +++ .../src/components/identity/projects.rs | 125 ++++++++++++++++++ .../src/components/resource_select_popup.rs | 4 + openstack_tui/src/mode.rs | 1 + 10 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 openstack_tui/src/components/identity.rs create mode 100644 openstack_tui/src/components/identity/projects.rs diff --git a/openstack_tui/src/action.rs b/openstack_tui/src/action.rs index f6525be15..71d669dfe 100644 --- a/openstack_tui/src/action.rs +++ b/openstack_tui/src/action.rs @@ -24,6 +24,7 @@ pub enum Resource { ComputeServerConsoleOutput(String), ComputeQuota, IdentityAuthProjects(IdentityAuthProjectFilters), + IdentityProjects(IdentityProjectFilters), ImageImages(ImageFilters), NetworkNetworks(NetworkNetworkFilters), NetworkSubnets(NetworkSubnetFilters), @@ -118,6 +119,13 @@ impl fmt::Display for IdentityAuthProjectFilters { write!(f, "") } } +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct IdentityProjectFilters {} +impl fmt::Display for IdentityProjectFilters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ImageFilters { diff --git a/openstack_tui/src/app.rs b/openstack_tui/src/app.rs index bbdbb359f..f4685079b 100644 --- a/openstack_tui/src/app.rs +++ b/openstack_tui/src/app.rs @@ -25,9 +25,9 @@ use crate::{ components::{ cloud_select_popup::CloudSelect, compute::flavors::ComputeFlavors, compute::servers::ComputeServers, describe::Describe, error_popup::ErrorPopup, - header::Header, home::Home, image::images::Images, network::networks::NetworkNetworks, - network::subnets::NetworkSubnets, project_select_popup::ProjectSelect, - resource_select_popup::ResourceSelect, Component, + header::Header, home::Home, identity::projects::IdentityProjects, image::images::Images, + network::networks::NetworkNetworks, network::subnets::NetworkSubnets, + project_select_popup::ProjectSelect, resource_select_popup::ResourceSelect, Component, }, config::Config, mode::Mode, @@ -73,6 +73,7 @@ impl App { let describe_component: Box = Box::new(Describe::new()); let compute_servers_component: Box = Box::new(ComputeServers::new()); let compute_flavors_component: Box = Box::new(ComputeFlavors::new()); + let identity_projects_component: Box = Box::new(IdentityProjects::new()); let image_images_component: Box = Box::new(Images::new()); let network_component: Box = Box::new(NetworkNetworks::new()); let subnet_component: Box = Box::new(NetworkSubnets::new()); @@ -99,6 +100,7 @@ impl App { (Mode::ComputeFlavors, compute_flavors_component), (Mode::NetworkNetworks, network_component), (Mode::NetworkSubnets, subnet_component), + (Mode::IdentityProjects, identity_projects_component), (Mode::ImageImages, image_images_component), ]), header: Box::new(Header::new()), diff --git a/openstack_tui/src/cloud_services.rs b/openstack_tui/src/cloud_services.rs index bfdcd86e5..748ab4abc 100644 --- a/openstack_tui/src/cloud_services.rs +++ b/openstack_tui/src/cloud_services.rs @@ -16,8 +16,8 @@ use eyre::Result; use serde_json::Value; use crate::action::{ - ComputeFlavorFilters, ComputeServerFilters, IdentityAuthProjectFilters, ImageFilters, - NetworkNetworkFilters, NetworkSubnetFilters, + ComputeFlavorFilters, ComputeServerFilters, IdentityAuthProjectFilters, IdentityProjectFilters, + ImageFilters, NetworkNetworkFilters, NetworkSubnetFilters, }; mod compute; @@ -33,6 +33,11 @@ pub trait ComputeExt { } pub trait IdentityExt { + async fn get_identity_projects( + &mut self, + _filters: &IdentityProjectFilters, + ) -> Result>; + async fn get_identity_auth_projects( &mut self, filters: &IdentityAuthProjectFilters, diff --git a/openstack_tui/src/cloud_services/identity.rs b/openstack_tui/src/cloud_services/identity.rs index 4fa085bc5..6b6173144 100644 --- a/openstack_tui/src/cloud_services/identity.rs +++ b/openstack_tui/src/cloud_services/identity.rs @@ -17,11 +17,29 @@ use serde_json::Value; use openstack_sdk::api::QueryAsync; -use crate::action::IdentityAuthProjectFilters; +use crate::action::{IdentityAuthProjectFilters, IdentityProjectFilters}; use crate::cloud_services::IdentityExt; use crate::cloud_worker::Cloud; impl IdentityExt for Cloud { + async fn get_identity_projects( + &mut self, + _filters: &IdentityProjectFilters, + ) -> Result> { + if let Some(session) = &self.cloud { + let ep_builder = openstack_sdk::api::identity::v3::project::list::Request::builder(); + + //if let Some(vis) = &filters.visibility { + // ep_builder.visibility(vis); + //} + let ep = ep_builder.build()?; + let res: Vec = ep.query_async(session).await?; + //let res: Vec = ep.query_async(session).await?; + return Ok(res); + } + Ok(Vec::new()) + } + async fn get_identity_auth_projects( &mut self, _filters: &IdentityAuthProjectFilters, diff --git a/openstack_tui/src/cloud_worker.rs b/openstack_tui/src/cloud_worker.rs index fe30c7bb8..104381e5f 100644 --- a/openstack_tui/src/cloud_worker.rs +++ b/openstack_tui/src/cloud_worker.rs @@ -207,6 +207,17 @@ impl Cloud { )))?, } } + Resource::IdentityProjects(ref filters) => { + match self.get_identity_projects(filters).await { + Ok(data) => { + app_tx.send(Action::ResourcesData { resource, data })? + } + Err(err) => app_tx.send(Action::Error(format!( + "Failed to fetch available projects: {:?}", + err + )))?, + } + } Resource::ImageImages(ref filters) => { match self.get_image_images(filters).await { Ok(data) => { diff --git a/openstack_tui/src/components.rs b/openstack_tui/src/components.rs index 8b03077d0..20fc1d733 100644 --- a/openstack_tui/src/components.rs +++ b/openstack_tui/src/components.rs @@ -28,6 +28,7 @@ pub mod describe; pub mod error_popup; pub mod header; pub mod home; +pub mod identity; pub mod image; pub mod network; pub mod project_select_popup; diff --git a/openstack_tui/src/components/identity.rs b/openstack_tui/src/components/identity.rs new file mode 100644 index 000000000..bf514308d --- /dev/null +++ b/openstack_tui/src/components/identity.rs @@ -0,0 +1,15 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod projects; diff --git a/openstack_tui/src/components/identity/projects.rs b/openstack_tui/src/components/identity/projects.rs new file mode 100644 index 000000000..5605b4ad1 --- /dev/null +++ b/openstack_tui/src/components/identity/projects.rs @@ -0,0 +1,125 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; +use eyre::Result; +use ratatui::prelude::*; +use serde::Deserialize; +use structable_derive::StructTable; +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + action::{Action, IdentityProjectFilters, Resource}, + components::{table_view::TableViewComponentBase, Component}, + config::Config, + mode::Mode, + utils::{OutputConfig, StructTable}, +}; + +const TITLE: &str = "Identity Projects"; + +#[derive(Deserialize, StructTable)] +pub struct ProjectData { + #[structable(title = "Name")] + name: String, + #[structable(title = "ID")] + id: String, + #[structable(title = "Parent ID")] + parent_id: String, + #[structable(title = "Enabled")] + enabled: bool, +} + +pub type IdentityProjects<'a> = TableViewComponentBase<'a, ProjectData, IdentityProjectFilters>; + +impl<'a> Component for IdentityProjects<'a> { + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.set_config(config)?; + Ok(()) + } + + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.set_command_tx(tx); + Ok(()) + } + + fn update(&mut self, action: Action, current_mode: Mode) -> Result> { + match action { + Action::CloudChangeScope(_) => { + self.set_loading(true); + } + Action::ConnectedToCloud(_) => { + self.set_loading(true); + self.set_data(Vec::new())?; + if let Mode::IdentityProjects = current_mode { + return Ok(Some(Action::RequestCloudResource( + Resource::IdentityProjects(self.get_filters().clone()), + ))); + } + } + Action::Mode(Mode::IdentityProjects) | Action::Refresh => { + self.set_loading(true); + return Ok(Some(Action::RequestCloudResource( + Resource::IdentityProjects(self.get_filters().clone()), + ))); + } + Action::Tick => { + self.app_tick()?; + if let Mode::IdentityProjects = current_mode { + return Ok(Some(Action::RequestCloudResource( + Resource::IdentityProjects(self.get_filters().clone()), + ))); + } + } + Action::Render => self.render_tick()?, + Action::ResourcesData { + resource: Resource::IdentityProjects(_), + data, + } => { + self.set_data(data)?; + } + _ => {} + }; + Ok(None) + } + + fn handle_key_events(&mut self, key: KeyEvent) -> Result> { + match key.code { + KeyCode::Down => self.cursor_down()?, + KeyCode::Up => self.cursor_up()?, + KeyCode::Home => self.cursor_first()?, + KeyCode::End => self.cursor_last()?, + KeyCode::PageUp => self.cursor_page_up()?, + KeyCode::PageDown => self.cursor_page_down()?, + KeyCode::Left => self.cursor_left()?, + KeyCode::Right => self.cursor_right()?, + KeyCode::Tab => self.key_tab()?, + _ => {} + } + if key.kind == KeyEventKind::Press && key.code == KeyCode::Enter { + if let Some(x) = self.get_selected_raw() { + return Ok(Some(Action::Describe(x.clone()))); + } + } + Ok(None) + } + + fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { + let areas = Layout::vertical([Constraint::Min(5), Constraint::Length(3)]).split(area); + + self.render_content(TITLE, f, areas[0])?; + self.render_footer(f, areas[1]); + Ok(()) + } +} diff --git a/openstack_tui/src/components/resource_select_popup.rs b/openstack_tui/src/components/resource_select_popup.rs index 9a35c5b5f..6d9a87fe3 100644 --- a/openstack_tui/src/components/resource_select_popup.rs +++ b/openstack_tui/src/components/resource_select_popup.rs @@ -61,6 +61,10 @@ impl ResourceSelect { ("Servers", Mode::ComputeServers), ]), ), + ( + "Identity", + HashMap::from([("Projects", Mode::IdentityProjects)]), + ), ("Image", HashMap::from([("Images", Mode::ImageImages)])), ( "Network", diff --git a/openstack_tui/src/mode.rs b/openstack_tui/src/mode.rs index 9eafe3ca1..e0e846a3e 100644 --- a/openstack_tui/src/mode.rs +++ b/openstack_tui/src/mode.rs @@ -22,6 +22,7 @@ pub enum Mode { Describe, ComputeFlavors, ComputeServers, + IdentityProjects, ImageImages, NetworkNetworks, NetworkSubnets,