feat: Full CLI and TUI support for the Lidarr Indexers tab

This commit is contained in:
2026-01-14 13:30:51 -07:00
parent 8abcf44866
commit c74d5936d2
91 changed files with 9481 additions and 166 deletions
@@ -0,0 +1,169 @@
use std::sync::atomic::Ordering;
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_INDEXER_BLOCKS};
use crate::render_selectable_input_box;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Size;
use crate::ui::{DrawUi, draw_popup};
use ratatui::Frame;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
#[cfg(test)]
#[path = "edit_indexer_ui_tests.rs"]
mod edit_indexer_ui_tests;
pub(super) struct EditIndexerUi;
impl DrawUi for EditIndexerUi {
fn accepts(route: Route) -> bool {
let Route::Lidarr(active_lidarr_block, _) = route else {
return false;
};
EDIT_INDEXER_BLOCKS.contains(&active_lidarr_block)
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
draw_popup(f, app, draw_edit_indexer_prompt, Size::WideLargePrompt);
}
}
fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let block = title_block_centered("Edit Indexer");
let yes_no_value = app.data.lidarr_data.prompt_confirm;
let selected_block = app.data.lidarr_data.selected_block.get_active_block();
let highlight_yes_no = selected_block == ActiveLidarrBlock::EditIndexerConfirmPrompt;
let edit_indexer_modal_option = &app.data.lidarr_data.edit_indexer_modal;
let protocol = &app.data.lidarr_data.indexers.current_selection().protocol;
if edit_indexer_modal_option.is_some() {
f.render_widget(block, area);
let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap();
let [settings_area, buttons_area] =
Layout::vertical([Constraint::Fill(1), Constraint::Length(3)])
.margin(1)
.areas(area);
let [left_side_area, right_side_area] =
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
.margin(1)
.areas(settings_area);
let [
name_area,
rss_area,
auto_search_area,
interactive_search_area,
priority_area,
] = Layout::vertical([
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
])
.areas(left_side_area);
let [url_area, api_key_area, seed_ratio_area, tags_area] = Layout::vertical([
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
])
.areas(right_side_area);
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
let priority = edit_indexer_modal.priority.to_string();
let name_input_box = InputBox::new(&edit_indexer_modal.name.text)
.offset(edit_indexer_modal.name.offset.load(Ordering::SeqCst))
.label("Name")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerNameInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerNameInput);
let url_input_box = InputBox::new(&edit_indexer_modal.url.text)
.offset(edit_indexer_modal.url.offset.load(Ordering::SeqCst))
.label("URL")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerUrlInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerUrlInput);
let api_key_input_box = InputBox::new(&edit_indexer_modal.api_key.text)
.offset(edit_indexer_modal.api_key.offset.load(Ordering::SeqCst))
.label("API Key")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerApiKeyInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerApiKeyInput);
let tags_input_box = InputBox::new(&edit_indexer_modal.tags.text)
.offset(edit_indexer_modal.tags.offset.load(Ordering::SeqCst))
.label("Tags")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerTagsInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerTagsInput);
let priority_input_box = InputBox::new(&priority)
.cursor_after_string(false)
.label("Indexer Priority ▴▾")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerPriorityInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerPriorityInput);
render_selectable_input_box!(name_input_box, f, name_area);
render_selectable_input_box!(url_input_box, f, url_area);
render_selectable_input_box!(api_key_input_box, f, api_key_area);
if protocol == "torrent" {
let seed_ratio_input_box = InputBox::new(&edit_indexer_modal.seed_ratio.text)
.offset(edit_indexer_modal.seed_ratio.offset.load(Ordering::SeqCst))
.label("Seed Ratio")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerSeedRatioInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerSeedRatioInput);
let tags_input_box = InputBox::new(&edit_indexer_modal.tags.text)
.offset(edit_indexer_modal.tags.offset.load(Ordering::SeqCst))
.label("Tags")
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerTagsInput)
.selected(active_lidarr_block == ActiveLidarrBlock::EditIndexerTagsInput);
render_selectable_input_box!(seed_ratio_input_box, f, seed_ratio_area);
render_selectable_input_box!(tags_input_box, f, tags_area);
render_selectable_input_box!(priority_input_box, f, priority_area);
} else {
render_selectable_input_box!(tags_input_box, f, seed_ratio_area);
render_selectable_input_box!(priority_input_box, f, tags_area);
}
let rss_checkbox = Checkbox::new("Enable RSS")
.checked(edit_indexer_modal.enable_rss.unwrap_or_default())
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerToggleEnableRss);
let auto_search_checkbox = Checkbox::new("Enable Automatic Search")
.checked(
edit_indexer_modal
.enable_automatic_search
.unwrap_or_default(),
)
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch);
let interactive_search_checkbox = Checkbox::new("Enable Interactive Search")
.checked(
edit_indexer_modal
.enable_interactive_search
.unwrap_or_default(),
)
.highlighted(selected_block == ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch);
let [save_area, cancel_area] =
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
f.render_widget(rss_checkbox, rss_area);
f.render_widget(auto_search_checkbox, auto_search_area);
f.render_widget(interactive_search_checkbox, interactive_search_area);
f.render_widget(save_button, save_area);
f.render_widget(cancel_button, cancel_area);
}
} else {
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
}
}
@@ -0,0 +1,81 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::BlockSelectionState;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
};
use crate::models::servarr_models::Indexer;
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_edit_indexer_ui_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if EDIT_INDEXER_BLOCKS.contains(&active_lidarr_block) {
assert!(EditIndexerUi::accepts(active_lidarr_block.into()));
} else {
assert!(!EditIndexerUi::accepts(active_lidarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::models::servarr_data::lidarr::lidarr_data::EDIT_INDEXER_NZB_SELECTION_BLOCKS;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer;
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use super::*;
#[test]
fn test_edit_indexer_ui_renders_loading() {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
app.data.lidarr_data.edit_indexer_modal = None;
app.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
EditIndexerUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_edit_indexer_ui_renders_edit_torrent_indexer() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
EditIndexerUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_edit_indexer_ui_renders_edit_usenet_indexer() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
app.data.lidarr_data.indexers.set_items(vec![Indexer {
protocol: "usenet".into(),
..indexer()
}]);
app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
EditIndexerUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -0,0 +1,117 @@
use ratatui::Frame;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::render_selectable_input_box;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Size;
use crate::ui::{DrawUi, draw_popup};
#[cfg(test)]
#[path = "indexer_settings_ui_tests.rs"]
mod indexer_settings_ui_tests;
pub(super) struct IndexerSettingsUi;
impl DrawUi for IndexerSettingsUi {
fn accepts(route: Route) -> bool {
let Route::Lidarr(active_lidarr_block, _) = route else {
return false;
};
INDEXER_SETTINGS_BLOCKS.contains(&active_lidarr_block)
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
draw_popup(f, app, draw_edit_indexer_settings_prompt, Size::LargePrompt);
}
}
fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let block = title_block_centered("Configure All Indexer Settings");
let yes_no_value = app.data.lidarr_data.prompt_confirm;
let selected_block = app.data.lidarr_data.selected_block.get_active_block();
let highlight_yes_no = selected_block == ActiveLidarrBlock::IndexerSettingsConfirmPrompt;
let indexer_settings_option = &app.data.lidarr_data.indexer_settings;
if indexer_settings_option.is_some() {
f.render_widget(block, area);
let indexer_settings = indexer_settings_option.as_ref().unwrap();
let [
_,
min_age_area,
retention_area,
max_size_area,
rss_sync_area,
_,
buttons_area,
] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(3),
])
.margin(1)
.areas(area);
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
let min_age = indexer_settings.minimum_age.to_string();
let retention = indexer_settings.retention.to_string();
let max_size = indexer_settings.maximum_size.to_string();
let rss_sync_interval = indexer_settings.rss_sync_interval.to_string();
let min_age_text_box = InputBox::new(&min_age)
.cursor_after_string(false)
.label("Minimum Age (minutes) ▴▾")
.highlighted(selected_block == ActiveLidarrBlock::IndexerSettingsMinimumAgeInput)
.selected(active_lidarr_block == ActiveLidarrBlock::IndexerSettingsMinimumAgeInput);
let retention_input_box = InputBox::new(&retention)
.cursor_after_string(false)
.label("Retention (days) ▴▾")
.highlighted(selected_block == ActiveLidarrBlock::IndexerSettingsRetentionInput)
.selected(active_lidarr_block == ActiveLidarrBlock::IndexerSettingsRetentionInput);
let max_size_input_box = InputBox::new(&max_size)
.cursor_after_string(false)
.label("Maximum Size (MB) ▴▾")
.highlighted(selected_block == ActiveLidarrBlock::IndexerSettingsMaximumSizeInput)
.selected(active_lidarr_block == ActiveLidarrBlock::IndexerSettingsMaximumSizeInput);
let rss_sync_interval_input_box = InputBox::new(&rss_sync_interval)
.cursor_after_string(false)
.label("RSS Sync Interval (minutes) ▴▾")
.highlighted(selected_block == ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput)
.selected(active_lidarr_block == ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput);
render_selectable_input_box!(min_age_text_box, f, min_age_area);
render_selectable_input_box!(retention_input_box, f, retention_area);
render_selectable_input_box!(max_size_input_box, f, max_size_area);
render_selectable_input_box!(rss_sync_interval_input_box, f, rss_sync_area);
}
let [save_area, cancel_area] =
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
f.render_widget(save_button, save_area);
f.render_widget(cancel_button, cancel_area);
} else {
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
}
}
@@ -0,0 +1,44 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::BlockSelectionState;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS,
};
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_indexer_settings_ui_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if INDEXER_SETTINGS_BLOCKS.contains(&active_lidarr_block) {
assert!(IndexerSettingsUi::accepts(active_lidarr_block.into()));
} else {
assert!(!IndexerSettingsUi::accepts(active_lidarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use super::*;
#[test]
fn test_indexer_settings_ui_renders_indexer_settings() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::IndexerSettingsMinimumAgeInput.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexerSettingsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -0,0 +1,156 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, EDIT_INDEXER_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXERS_BLOCKS,
};
use crate::models::servarr_models::Indexer;
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::indexers::IndexersUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_indexers_ui_accepts() {
let mut indexers_blocks = Vec::new();
indexers_blocks.extend(INDEXERS_BLOCKS);
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
indexers_blocks.extend(EDIT_INDEXER_BLOCKS);
indexers_blocks.push(ActiveLidarrBlock::TestAllIndexers);
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if indexers_blocks.contains(&active_lidarr_block) {
assert!(IndexersUi::accepts(active_lidarr_block.into()));
} else {
assert!(!IndexersUi::accepts(active_lidarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::models::BlockSelectionState;
use crate::models::servarr_data::lidarr::lidarr_data::{
EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
};
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer;
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use rstest::rstest;
use super::*;
#[test]
fn test_indexers_ui_renders_loading() {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_indexers_ui_renders_loading_test_results() {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::TestIndexer.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_indexers_ui_renders_loading_test_results_when_indexer_test_errors_is_none() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::TestIndexer.into());
app.data.lidarr_data.indexer_test_errors = None;
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_indexers_ui_renders_empty_indexers() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[rstest]
fn test_indexers_ui_renders(
#[values(
ActiveLidarrBlock::DeleteIndexerPrompt,
ActiveLidarrBlock::Indexers,
ActiveLidarrBlock::TestIndexer
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("indexers_ui_{active_lidarr_block}"), output);
}
#[test]
fn test_indexers_ui_renders_test_all_over_indexers() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_indexers_ui_renders_edit_usenet_indexer_over_indexers() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS);
app.data.lidarr_data.indexers.set_items(vec![Indexer {
protocol: "usenet".into(),
..indexer()
}]);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_indexers_ui_renders_edit_torrent_indexer_over_indexers() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
IndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
+184
View File
@@ -0,0 +1,184 @@
use crate::ui::styles::success_style;
use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, INDEXERS_BLOCKS};
use crate::models::servarr_models::Indexer;
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
use crate::ui::lidarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::lidarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{layout_block_top_border, title_block};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
mod edit_indexer_ui;
mod indexer_settings_ui;
mod test_all_indexers_ui;
#[cfg(test)]
#[path = "indexers_ui_tests.rs"]
mod indexers_ui_tests;
pub(super) struct IndexersUi;
impl DrawUi for IndexersUi {
fn accepts(route: Route) -> bool {
if let Route::Lidarr(active_lidarr_block, _) = route {
return EditIndexerUi::accepts(route)
|| IndexerSettingsUi::accepts(route)
|| TestAllIndexersUi::accepts(route)
|| INDEXERS_BLOCKS.contains(&active_lidarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let route = app.get_current_route();
draw_indexers(f, app, area);
match route {
_ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, area),
_ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, area),
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
Route::Lidarr(active_lidarr_block, _) => match active_lidarr_block {
ActiveLidarrBlock::TestIndexer => {
if app.is_loading || app.data.lidarr_data.indexer_test_errors.is_none() {
let loading_popup = Popup::new(LoadingBlock::new(
app.is_loading || app.data.lidarr_data.indexer_test_errors.is_none(),
title_block("Testing Indexer"),
))
.size(Size::LargeMessage);
f.render_widget(loading_popup, f.area());
} else {
let popup = {
let result = app
.data
.lidarr_data
.indexer_test_errors
.as_ref()
.expect("Test result is unpopulated");
if !result.is_empty() {
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
} else {
let message = Message::new("Indexer test succeeded!")
.title("Success")
.style(success_style().bold());
Popup::new(message).size(Size::Message)
}
};
f.render_widget(popup, f.area());
}
}
ActiveLidarrBlock::DeleteIndexerPrompt => {
let prompt = format!(
"Do you really want to delete this indexer: \n{}?",
app
.data
.lidarr_data
.indexers
.current_selection()
.name
.clone()
.unwrap_or_default()
);
let confirmation_prompt = ConfirmationPrompt::new()
.title("Delete Indexer")
.prompt(&prompt)
.yes_no_value(app.data.lidarr_data.prompt_confirm);
f.render_widget(
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
f.area(),
);
}
_ => (),
},
_ => (),
}
}
}
fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let indexers_row_mapping = |indexer: &'_ Indexer| {
let Indexer {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
priority,
tags,
..
} = indexer;
let bool_to_text = |flag: bool| {
if flag {
return Text::from("Enabled").success();
}
Text::from("Disabled").failure()
};
let rss = bool_to_text(*enable_rss);
let automatic_search = bool_to_text(*enable_automatic_search);
let interactive_search = bool_to_text(*enable_interactive_search);
let empty_tag = String::new();
let tags: String = tags
.iter()
.map(|tag_id| {
app
.data
.lidarr_data
.tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap_or(&empty_tag)
.clone()
})
.collect::<Vec<String>>()
.join(", ");
Row::new(vec![
Cell::from(name.clone().unwrap_or_default()),
Cell::from(rss),
Cell::from(automatic_search),
Cell::from(interactive_search),
Cell::from(priority.to_string()),
Cell::from(tags),
])
.primary()
};
let indexers_table = ManagarrTable::new(
Some(&mut app.data.lidarr_data.indexers),
indexers_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers([
"Indexer",
"RSS",
"Automatic Search",
"Interactive Search",
"Priority",
"Tags",
])
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(23),
]);
f.render_widget(indexers_table, area);
}
@@ -0,0 +1,42 @@
---
source: src/ui/lidarr_ui/indexers/edit_indexer_ui_tests.rs
expression: output
---
╭──────────────────────────────────────────────── Edit Indexer ─────────────────────────────────────────────────╮
│ │
│ ╭─────────────────────────╮ ╭─────────────────────────╮ │
│ Name: │DrunkenSlug │ URL: │http://127.0.0.1:9696/1/ │ │
│ ╰─────────────────────────╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable RSS: │ ✔ │ API Key: │someApiKey │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Automatic Search: │ ✔ │ Seed Ratio: │ratio │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Interactive Search: │ ✔ │ Tags: │25 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭─────────────────────────╮ │
│ Indexer Priority ▴▾: │1 │ │
│ ╰─────────────────────────╯ │
│ │
│ │
│ │
│ │
│ ╭───────────────────────────╮╭──────────────────────────╮ │
│ │ Save ││ Cancel │ │
│ ╰───────────────────────────╯╰──────────────────────────╯ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,42 @@
---
source: src/ui/lidarr_ui/indexers/edit_indexer_ui_tests.rs
expression: output
---
╭──────────────────────────────────────────────── Edit Indexer ─────────────────────────────────────────────────╮
│ │
│ ╭─────────────────────────╮ ╭─────────────────────────╮ │
│ Name: │DrunkenSlug │ URL: │http://127.0.0.1:9696/1/ │ │
│ ╰─────────────────────────╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable RSS: │ ✔ │ API Key: │someApiKey │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Automatic Search: │ ✔ │ Tags: │25 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Interactive Search: │ ✔ │ Indexer Priority ▴▾: │1 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ ╭───────────────────────────╮╭──────────────────────────╮ │
│ │ Save ││ Cancel │ │
│ ╰───────────────────────────╯╰──────────────────────────╯ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,42 @@
---
source: src/ui/lidarr_ui/indexers/edit_indexer_ui_tests.rs
expression: output
---
╭──────────────────────────────────────────────── Edit Indexer ─────────────────────────────────────────────────╮
│ │
│ │
│ Loading ... │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,40 @@
---
source: src/ui/lidarr_ui/indexers/indexer_settings_ui_tests.rs
expression: output
---
╭─────────────────── Configure All Indexer Settings ───────────────────╮
│ │
│ │
│ │
│ ╭────────────────────────────────╮ │
│ Minimum Age (minutes) ▴▾: │1 │ │
│ ╰────────────────────────────────╯ │
│ ╭────────────────────────────────╮ │
│ Retention (days) ▴▾: │1 │ │
│ ╰────────────────────────────────╯ │
│ ╭────────────────────────────────╮ │
│ Maximum Size (MB) ▴▾: │12345 │ │
│ ╰────────────────────────────────╯ │
│ ╭────────────────────────────────╮ │
│ RSS Sync Interval (minutes) ▴▾: │60 │ │
│ ╰────────────────────────────────╯ │
│ │
│ │
│ ╭────────────────╮╭────────────────╮ │
│ │ Save ││ Cancel │ │
│ ╰────────────────╯╰────────────────╯ │
╰────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,38 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭──────────────────── Delete Indexer ─────────────────────╮
│ Do you really want to delete this indexer: │
│ Test Indexer? │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│╭────────────────────────────╮╭───────────────────────────╮│
││ Yes ││ No ││
│╰────────────────────────────╯╰───────────────────────────╯│
╰───────────────────────────────────────────────────────────╯
@@ -0,0 +1,7 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
@@ -0,0 +1,35 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭─────────────── Error ───────────────╮
│ error │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭────────────── Success ──────────────╮
│ Indexer test succeeded! │
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,42 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭──────────────────────────────────────────────── Edit Indexer ─────────────────────────────────────────────────╮
│ │
│ ╭─────────────────────────╮ ╭─────────────────────────╮ │
│ Name: │DrunkenSlug │ URL: │http://127.0.0.1:9696/1/ │ │
│ ╰─────────────────────────╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable RSS: │ ✔ │ API Key: │someApiKey │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Automatic Search: │ ✔ │ Seed Ratio: │ratio │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Interactive Search: │ ✔ │ Tags: │25 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭─────────────────────────╮ │
│ Indexer Priority ▴▾: │1 │ │
│ ╰─────────────────────────╯ │
│ │
│ │
│ │
│ │
│ ╭───────────────────────────╮╭──────────────────────────╮ │
│ │ Save ││ Cancel │ │
│ ╰───────────────────────────╯╰──────────────────────────╯ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,42 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭──────────────────────────────────────────────── Edit Indexer ─────────────────────────────────────────────────╮
│ │
│ ╭─────────────────────────╮ ╭─────────────────────────╮ │
│ Name: │DrunkenSlug │ URL: │http://127.0.0.1:9696/1/ │ │
│ ╰─────────────────────────╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable RSS: │ ✔ │ API Key: │someApiKey │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Automatic Search: │ ✔ │ Tags: │25 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ ╭───╮ ╭─────────────────────────╮ │
│ Enable Interactive Search: │ ✔ │ Indexer Priority ▴▾: │1 │ │
│ ╰───╯ ╰─────────────────────────╯ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ ╭───────────────────────────╮╭──────────────────────────╮ │
│ │ Save ││ Cancel │ │
│ ╰───────────────────────────╯╰──────────────────────────╯ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,5 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,8 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
@@ -0,0 +1,35 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
╭ Testing Indexer ────────────────────╮
│ │
│ │
│ Loading ... │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,35 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭ Testing Indexer ────────────────────╮
│ │
│ │
│ Loading ... │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,48 @@
---
source: src/ui/lidarr_ui/indexers/indexers_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Indexer RSS Automatic Search Interactive Search Priority Tags
=> Test Indexer Enabled Enabled Enabled 25 alex
╭ Test All Indexers ─────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Indexer Pass/Fail Failure Messages │
│=> DrunkenSlug x Some failure │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,48 @@
---
source: src/ui/lidarr_ui/indexers/test_all_indexers_ui_tests.rs
expression: output
---
╭ Test All Indexers ─────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Indexer Pass/Fail Failure Messages │
│=> DrunkenSlug x Some failure │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,48 @@
---
source: src/ui/lidarr_ui/indexers/test_all_indexers_ui_tests.rs
expression: output
---
╭ Test All Indexers ─────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ │
│ Loading ... │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,79 @@
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{get_width_from_percentage, title_block};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::popup::Size;
use crate::ui::{DrawUi, draw_popup};
use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row};
#[cfg(test)]
#[path = "test_all_indexers_ui_tests.rs"]
mod test_all_indexers_ui_tests;
pub(super) struct TestAllIndexersUi;
impl DrawUi for TestAllIndexersUi {
fn accepts(route: Route) -> bool {
if let Route::Lidarr(active_lidarr_block, _) = route {
return active_lidarr_block == ActiveLidarrBlock::TestAllIndexers;
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
draw_popup(f, app, draw_test_all_indexers_test_results, Size::Large);
}
}
fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let is_loading = app.is_loading || app.data.lidarr_data.indexer_test_all_results.is_none();
let current_selection = if let Some(test_all_results) =
app.data.lidarr_data.indexer_test_all_results.as_ref()
&& !test_all_results.is_empty()
{
test_all_results.current_selection().clone()
} else {
IndexerTestResultModalItem::default()
};
f.render_widget(title_block("Test All Indexers"), area);
let test_results_row_mapping = |result: &IndexerTestResultModalItem| {
result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86),
*result == current_selection,
app.ui_scroll_tick_count == 0,
);
let pass_fail = if result.is_valid { "+" } else { "x" };
let row = Row::new(vec![
Cell::from(result.name.to_owned()),
Cell::from(pass_fail.to_owned()),
Cell::from(result.validation_failures.to_string()),
]);
if result.is_valid {
row.success()
} else {
row.failure()
}
};
let indexers_test_results_table = ManagarrTable::new(
app.data.lidarr_data.indexer_test_all_results.as_mut(),
test_results_row_mapping,
)
.loading(is_loading)
.margin(1)
.headers(["Indexer", "Pass/Fail", "Failure Messages"])
.constraints([
Constraint::Percentage(20),
Constraint::Percentage(10),
Constraint::Percentage(70),
]);
f.render_widget(indexers_test_results_table, area);
}
@@ -0,0 +1,52 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::ui::DrawUi;
use crate::ui::lidarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_test_all_indexers_ui_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if active_lidarr_block == ActiveLidarrBlock::TestAllIndexers {
assert!(TestAllIndexersUi::accepts(active_lidarr_block.into()));
} else {
assert!(!TestAllIndexersUi::accepts(active_lidarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use super::*;
#[test]
fn test_test_all_indexers_ui_renders_loading() {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
TestAllIndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_test_all_indexers_ui_renders() {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
TestAllIndexersUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
+1
View File
@@ -25,6 +25,7 @@ mod tests {
#[case(ActiveLidarrBlock::Downloads, 1)]
#[case(ActiveLidarrBlock::History, 2)]
#[case(ActiveLidarrBlock::RootFolders, 3)]
#[case(ActiveLidarrBlock::Indexers, 4)]
fn test_lidarr_ui_renders_lidarr_tabs(
#[case] active_lidarr_block: ActiveLidarrBlock,
#[case] index: usize,
+3
View File
@@ -24,6 +24,7 @@ use super::{
widgets::loading_block::LoadingBlock,
};
use crate::ui::lidarr_ui::downloads::DownloadsUi;
use crate::ui::lidarr_ui::indexers::IndexersUi;
use crate::ui::lidarr_ui::root_folders::RootFoldersUi;
use crate::{
app::App,
@@ -39,6 +40,7 @@ use crate::{
mod downloads;
mod history;
mod indexers;
mod library;
mod lidarr_ui_utils;
@@ -63,6 +65,7 @@ impl DrawUi for LidarrUi {
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
_ => (),
}
}
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │ Root Folders
│ Library │ Downloads │ History │ Root Folders │ Indexers
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Name ▼ Type Status Quality Profile Metadata Profile Albums Tracks Size Monitored Tags │
│=> Alex Person Continuing Lossless Standard 1 15/15 0.00 GB 🏷 alex │
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │ Root Folders
│ Library │ Downloads │ History │ Root Folders │ Indexers
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title Percent Complete Size Output Path Indexer Download Client │
│=> Test download title 50% 3.30 GB /nfs/music/alex/album kickass torrents transmission │
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │ Root Folders
│ Library │ Downloads │ History │ Root Folders │ Indexers
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Source Title ▼ Event Type Quality Date │
│=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC │
@@ -0,0 +1,54 @@
---
source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │ Root Folders │ Indexers │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Indexer RSS Automatic Search Interactive Search Priority Tags │
│=> Test Indexer Enabled Enabled Enabled 25 alex │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -3,7 +3,7 @@ source: src/ui/lidarr_ui/lidarr_ui_tests.rs
expression: output
---
╭ Artists ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ History │ Root Folders
│ Library │ Downloads │ History │ Root Folders │ Indexers
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Path Free Space Unmapped Folders │
│=> /nfs 204800.00 GB 0 │