feat: Support for multiple servarr definitions - no tests [skip ci]
This commit is contained in:
@@ -345,10 +345,13 @@ mod tests {
|
|||||||
fn test_servarr_config_default() {
|
fn test_servarr_config_default() {
|
||||||
let servarr_config = ServarrConfig::default();
|
let servarr_config = ServarrConfig::default();
|
||||||
|
|
||||||
|
assert!(servarr_config.name.is_empty());
|
||||||
assert_eq!(servarr_config.host, Some("localhost".to_string()));
|
assert_eq!(servarr_config.host, Some("localhost".to_string()));
|
||||||
assert_eq!(servarr_config.port, None);
|
assert_eq!(servarr_config.port, None);
|
||||||
assert_eq!(servarr_config.uri, None);
|
assert_eq!(servarr_config.uri, None);
|
||||||
|
assert_eq!(servarr_config.weight, None);
|
||||||
assert_eq!(servarr_config.api_token, Some(String::new()));
|
assert_eq!(servarr_config.api_token, Some(String::new()));
|
||||||
|
assert_eq!(servarr_config.api_token_file, None);
|
||||||
assert_eq!(servarr_config.ssl_cert_path, None);
|
assert_eq!(servarr_config.ssl_cert_path, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,18 +506,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_servarr_config_redacted_debug() {
|
fn test_servarr_config_redacted_debug() {
|
||||||
|
let name = "Servarr".to_owned();
|
||||||
let host = "localhost".to_owned();
|
let host = "localhost".to_owned();
|
||||||
let port = 1234;
|
let port = 1234;
|
||||||
let uri = "http://localhost:1234".to_owned();
|
let uri = "http://localhost:1234".to_owned();
|
||||||
|
let weight = 100;
|
||||||
let api_token = "thisisatest".to_owned();
|
let api_token = "thisisatest".to_owned();
|
||||||
let api_token_file = "/root/.config/api_token".to_owned();
|
let api_token_file = "/root/.config/api_token".to_owned();
|
||||||
let ssl_cert_path = "/some/path".to_owned();
|
let ssl_cert_path = "/some/path".to_owned();
|
||||||
let expected_str = format!("ServarrConfig {{ host: Some(\"{}\"), port: Some({}), uri: Some(\"{}\"), api_token: Some(\"***********\"), api_token_file: Some(\"{}\"), ssl_cert_path: Some(\"{}\") }}",
|
let expected_str = format!("ServarrConfig {{ name: \"{}\", host: Some(\"{}\"), port: Some({}), uri: Some(\"{}\"), weight: Some(\"{}\"), api_token: Some(\"***********\"), api_token_file: Some(\"{}\"), ssl_cert_path: Some(\"{}\") }}",
|
||||||
host, port, uri, api_token_file, ssl_cert_path);
|
name, host, port, uri, weight, api_token_file, ssl_cert_path);
|
||||||
let servarr_config = ServarrConfig {
|
let servarr_config = ServarrConfig {
|
||||||
|
name,
|
||||||
host: Some(host),
|
host: Some(host),
|
||||||
port: Some(port),
|
port: Some(port),
|
||||||
uri: Some(uri),
|
uri: Some(uri),
|
||||||
|
weight: Some(weight),
|
||||||
api_token: Some(api_token),
|
api_token: Some(api_token),
|
||||||
api_token_file: Some(api_token_file),
|
api_token_file: Some(api_token_file),
|
||||||
ssl_cert_path: Some(ssl_cert_path),
|
ssl_cert_path: Some(ssl_cert_path),
|
||||||
|
|||||||
+86
-50
@@ -1,10 +1,11 @@
|
|||||||
use std::{fs, process};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use itertools::Itertools;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{fs, process};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use veil::Redact;
|
use veil::Redact;
|
||||||
@@ -40,7 +41,6 @@ pub struct App<'a> {
|
|||||||
pub should_refresh: bool,
|
pub should_refresh: bool,
|
||||||
pub should_ignore_quit_key: bool,
|
pub should_ignore_quit_key: bool,
|
||||||
pub cli_mode: bool,
|
pub cli_mode: bool,
|
||||||
pub config: AppConfig,
|
|
||||||
pub data: Data<'a>,
|
pub data: Data<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,35 +52,50 @@ impl App<'_> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut server_tabs = Vec::new();
|
let mut server_tabs = Vec::new();
|
||||||
|
|
||||||
if config.radarr.is_some() {
|
if let Some(radarr_configs) = config.radarr {
|
||||||
server_tabs.push(TabRoute {
|
for radarr_config in radarr_configs {
|
||||||
title: "Radarr",
|
server_tabs.push(TabRoute {
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
title: radarr_config.name.clone(),
|
||||||
help: format!(
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
help: format!(
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
"<↑↓> scroll | ←→ change tab | {} ",
|
||||||
),
|
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||||
contextual_help: None,
|
),
|
||||||
});
|
contextual_help: None,
|
||||||
|
config: Some(radarr_config),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.sonarr.is_some() {
|
if let Some(sonarr_configs) = config.sonarr {
|
||||||
server_tabs.push(TabRoute {
|
for sonarr_config in sonarr_configs {
|
||||||
title: "Sonarr",
|
server_tabs.push(TabRoute {
|
||||||
route: ActiveSonarrBlock::Series.into(),
|
title: sonarr_config.name.clone(),
|
||||||
help: format!(
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
help: format!(
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
"<↑↓> scroll | ←→ change tab | {} ",
|
||||||
),
|
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||||
contextual_help: None,
|
),
|
||||||
});
|
contextual_help: None,
|
||||||
|
config: Some(sonarr_config),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let weight_sorted_tabs = server_tabs
|
||||||
|
.into_iter()
|
||||||
|
.sorted_by(|tab1, tab2| {
|
||||||
|
Ord::cmp(
|
||||||
|
tab1.config.as_ref().unwrap().weight.as_ref().unwrap_or(&0),
|
||||||
|
tab2.config.as_ref().unwrap().weight.as_ref().unwrap_or(&0),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
App {
|
App {
|
||||||
network_tx: Some(network_tx),
|
network_tx: Some(network_tx),
|
||||||
config,
|
|
||||||
cancellation_token,
|
cancellation_token,
|
||||||
server_tabs: TabState::new(server_tabs),
|
server_tabs: TabState::new(weight_sorted_tabs),
|
||||||
..App::default()
|
..App::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,22 +192,24 @@ impl Default for App<'_> {
|
|||||||
is_first_render: true,
|
is_first_render: true,
|
||||||
server_tabs: TabState::new(vec![
|
server_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Radarr",
|
title: "Radarr".to_owned(),
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
help: format!(
|
help: format!(
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
"<↑↓> scroll | ←→ change tab | {} ",
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||||
),
|
),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Sonarr",
|
title: "Sonarr".to_owned(),
|
||||||
route: ActiveSonarrBlock::Series.into(),
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
help: format!(
|
help: format!(
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
"<↑↓> scroll | ←→ change tab | {} ",
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||||
),
|
),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
tick_until_poll: 400,
|
tick_until_poll: 400,
|
||||||
@@ -203,7 +220,6 @@ impl Default for App<'_> {
|
|||||||
should_refresh: false,
|
should_refresh: false,
|
||||||
should_ignore_quit_key: false,
|
should_ignore_quit_key: false,
|
||||||
cli_mode: false,
|
cli_mode: false,
|
||||||
config: AppConfig::default(),
|
|
||||||
data: Data::default(),
|
data: Data::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,8 +233,8 @@ pub struct Data<'a> {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub radarr: Option<ServarrConfig>,
|
pub radarr: Option<Vec<ServarrConfig>>,
|
||||||
pub sonarr: Option<ServarrConfig>,
|
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
@@ -230,12 +246,12 @@ impl AppConfig {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(radarr_config) = &self.radarr {
|
if let Some(radarr_configs) = &self.radarr {
|
||||||
radarr_config.validate();
|
radarr_configs.iter().for_each(|config| config.validate());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sonarr_config) = &self.sonarr {
|
if let Some(sonarr_configs) = &self.sonarr {
|
||||||
sonarr_config.validate();
|
sonarr_configs.iter().for_each(|config| config.validate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,36 +276,32 @@ impl AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_process_initialization(&mut self) {
|
pub fn post_process_initialization(&mut self) {
|
||||||
let fetch_token = |config: &mut ServarrConfig, name: &'static str| {
|
if let Some(radarr_configs) = self.radarr.as_mut() {
|
||||||
if let Some(api_token_file) = config.api_token_file.as_ref() {
|
for radarr_config in radarr_configs {
|
||||||
if !PathBuf::from(api_token_file).exists() {
|
radarr_config.post_process_initialization();
|
||||||
log_and_print_error(format!("The specified {} API token file", name));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let api_token = fs::read_to_string(api_token_file).map_err(|e| anyhow!(e)).unwrap();
|
|
||||||
config.api_token = Some(api_token.trim().to_owned());
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(radarr_config) = self.radarr.as_mut() {
|
|
||||||
fetch_token(radarr_config, "Radarr");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sonarr_config) = self.sonarr.as_mut() {
|
if let Some(sonarr_configs) = self.sonarr.as_mut() {
|
||||||
fetch_token(sonarr_config, "Sonarr");
|
for sonarr_config in sonarr_configs {
|
||||||
|
sonarr_config.post_process_initialization();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Redact, Deserialize, Serialize, Clone)]
|
#[derive(Redact, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
pub struct ServarrConfig {
|
pub struct ServarrConfig {
|
||||||
|
#[serde(default, deserialize_with = "deserialize_env_var")]
|
||||||
|
pub name: String,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
#[serde(default, deserialize_with = "deserialize_u16_env_var")]
|
#[serde(default, deserialize_with = "deserialize_u16_env_var")]
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub uri: Option<String>,
|
pub uri: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_u16_env_var")]
|
||||||
|
pub weight: Option<u16>,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
#[redact]
|
#[redact]
|
||||||
pub api_token: Option<String>,
|
pub api_token: Option<String>,
|
||||||
@@ -301,6 +313,11 @@ pub struct ServarrConfig {
|
|||||||
|
|
||||||
impl ServarrConfig {
|
impl ServarrConfig {
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
|
if self.name.is_empty() {
|
||||||
|
log_and_print_error("'name' is required for configuration".to_owned());
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if self.host.is_none() && self.uri.is_none() {
|
if self.host.is_none() && self.uri.is_none() {
|
||||||
log_and_print_error("'host' or 'uri' is required for configuration".to_owned());
|
log_and_print_error("'host' or 'uri' is required for configuration".to_owned());
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
@@ -313,14 +330,33 @@ impl ServarrConfig {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn post_process_initialization(&mut self) {
|
||||||
|
if let Some(api_token_file) = self.api_token_file.as_ref() {
|
||||||
|
if !PathBuf::from(api_token_file).exists() {
|
||||||
|
log_and_print_error(format!(
|
||||||
|
"The specified {} API token file does not exist",
|
||||||
|
api_token_file
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let api_token = fs::read_to_string(api_token_file)
|
||||||
|
.map_err(|e| anyhow!(e))
|
||||||
|
.unwrap();
|
||||||
|
self.api_token = Some(api_token.trim().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServarrConfig {
|
impl Default for ServarrConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ServarrConfig {
|
ServarrConfig {
|
||||||
|
name: String::new(),
|
||||||
host: Some("localhost".to_string()),
|
host: Some("localhost".to_string()),
|
||||||
port: None,
|
port: None,
|
||||||
uri: None,
|
uri: None,
|
||||||
|
weight: None,
|
||||||
api_token: Some(String::new()),
|
api_token: Some(String::new()),
|
||||||
api_token_file: None,
|
api_token_file: None,
|
||||||
ssl_cert_path: None,
|
ssl_cert_path: None,
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use crate::app::ServarrConfig;
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
use radarr_models::RadarrSerdeable;
|
use radarr_models::RadarrSerdeable;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -267,10 +268,11 @@ impl HorizontallyScrollableText {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct TabRoute {
|
pub struct TabRoute {
|
||||||
pub title: &'static str,
|
pub title: String,
|
||||||
pub route: Route,
|
pub route: Route,
|
||||||
pub help: String,
|
pub help: String,
|
||||||
pub contextual_help: Option<String>,
|
pub contextual_help: Option<String>,
|
||||||
|
pub config: Option<ServarrConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TabState {
|
pub struct TabState {
|
||||||
@@ -294,6 +296,10 @@ impl TabState {
|
|||||||
self.tabs[self.index].route
|
self.tabs[self.index].route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_active_config(&self) -> &Option<ServarrConfig> {
|
||||||
|
&self.tabs[self.index].config
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_active_tab_help(&self) -> &str {
|
pub fn get_active_tab_help(&self) -> &str {
|
||||||
&self.tabs[self.index].help
|
&self.tabs[self.index].help
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,86 +121,99 @@ impl<'a> Default for RadarrData<'a> {
|
|||||||
add_list_exclusion: false,
|
add_list_exclusion: false,
|
||||||
main_tabs: TabState::new(vec![
|
main_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Library",
|
title: "Library".to_string(),
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Collections",
|
title: "Collections".to_string(),
|
||||||
route: ActiveRadarrBlock::Collections.into(),
|
route: ActiveRadarrBlock::Collections.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Downloads",
|
title: "Downloads".to_string(),
|
||||||
route: ActiveRadarrBlock::Downloads.into(),
|
route: ActiveRadarrBlock::Downloads.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Blocklist",
|
title: "Blocklist".to_string(),
|
||||||
route: ActiveRadarrBlock::Blocklist.into(),
|
route: ActiveRadarrBlock::Blocklist.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Root Folders",
|
title: "Root Folders".to_string(),
|
||||||
route: ActiveRadarrBlock::RootFolders.into(),
|
route: ActiveRadarrBlock::RootFolders.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Indexers",
|
title: "Indexers".to_string(),
|
||||||
route: ActiveRadarrBlock::Indexers.into(),
|
route: ActiveRadarrBlock::Indexers.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "System",
|
title: "System".to_string(),
|
||||||
route: ActiveRadarrBlock::System.into(),
|
route: ActiveRadarrBlock::System.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
movie_info_tabs: TabState::new(vec![
|
movie_info_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Details",
|
title: "Details".to_string(),
|
||||||
route: ActiveRadarrBlock::MovieDetails.into(),
|
route: ActiveRadarrBlock::MovieDetails.into(),
|
||||||
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History".to_string(),
|
||||||
route: ActiveRadarrBlock::MovieHistory.into(),
|
route: ActiveRadarrBlock::MovieHistory.into(),
|
||||||
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "File",
|
title: "File".to_string(),
|
||||||
route: ActiveRadarrBlock::FileInfo.into(),
|
route: ActiveRadarrBlock::FileInfo.into(),
|
||||||
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Cast",
|
title: "Cast".to_string(),
|
||||||
route: ActiveRadarrBlock::Cast.into(),
|
route: ActiveRadarrBlock::Cast.into(),
|
||||||
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Crew",
|
title: "Crew".to_string(),
|
||||||
route: ActiveRadarrBlock::Crew.into(),
|
route: ActiveRadarrBlock::Crew.into(),
|
||||||
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&MOVIE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Manual Search",
|
title: "Manual Search".to_string(),
|
||||||
route: ActiveRadarrBlock::ManualSearch.into(),
|
route: ActiveRadarrBlock::ManualSearch.into(),
|
||||||
help: build_context_clue_string(&MANUAL_MOVIE_SEARCH_CONTEXT_CLUES),
|
help: build_context_clue_string(&MANUAL_MOVIE_SEARCH_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(
|
contextual_help: Some(build_context_clue_string(
|
||||||
&MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
&MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||||
)),
|
)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,28 +280,32 @@ impl Default for EpisodeDetailsModal {
|
|||||||
episode_releases: StatefulTable::default(),
|
episode_releases: StatefulTable::default(),
|
||||||
episode_details_tabs: TabState::new(vec![
|
episode_details_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Details",
|
title: "Details".to_string(),
|
||||||
route: ActiveSonarrBlock::EpisodeDetails.into(),
|
route: ActiveSonarrBlock::EpisodeDetails.into(),
|
||||||
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History".to_string(),
|
||||||
route: ActiveSonarrBlock::EpisodeHistory.into(),
|
route: ActiveSonarrBlock::EpisodeHistory.into(),
|
||||||
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "File",
|
title: "File".to_string(),
|
||||||
route: ActiveSonarrBlock::EpisodeFile.into(),
|
route: ActiveSonarrBlock::EpisodeFile.into(),
|
||||||
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: None,
|
contextual_help: None,
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Manual Search",
|
title: "Manual Search".to_string(),
|
||||||
route: ActiveSonarrBlock::ManualEpisodeSearch.into(),
|
route: ActiveSonarrBlock::ManualEpisodeSearch.into(),
|
||||||
help: build_context_clue_string(&MANUAL_EPISODE_SEARCH_CONTEXT_CLUES),
|
help: build_context_clue_string(&MANUAL_EPISODE_SEARCH_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
@@ -327,24 +331,27 @@ impl Default for SeasonDetailsModal {
|
|||||||
season_history: StatefulTable::default(),
|
season_history: StatefulTable::default(),
|
||||||
season_details_tabs: TabState::new(vec![
|
season_details_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Episodes",
|
title: "Episodes".to_string(),
|
||||||
route: ActiveSonarrBlock::SeasonDetails.into(),
|
route: ActiveSonarrBlock::SeasonDetails.into(),
|
||||||
help: build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES),
|
help: build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(
|
contextual_help: Some(build_context_clue_string(
|
||||||
&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES,
|
&SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES,
|
||||||
)),
|
)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History".to_string(),
|
||||||
route: ActiveSonarrBlock::SeasonHistory.into(),
|
route: ActiveSonarrBlock::SeasonHistory.into(),
|
||||||
help: build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES),
|
help: build_context_clue_string(&SEASON_HISTORY_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Manual Search",
|
title: "Manual Search".to_string(),
|
||||||
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
|
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
|
||||||
help: build_context_clue_string(&MANUAL_SEASON_SEARCH_CONTEXT_CLUES),
|
help: build_context_clue_string(&MANUAL_SEASON_SEARCH_CONTEXT_CLUES),
|
||||||
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DETAILS_CONTEXTUAL_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,60 +128,69 @@ impl<'a> Default for SonarrData<'a> {
|
|||||||
version: String::new(),
|
version: String::new(),
|
||||||
main_tabs: TabState::new(vec![
|
main_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Library",
|
title: "Library".to_string(),
|
||||||
route: ActiveSonarrBlock::Series.into(),
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&SERIES_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SERIES_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Downloads",
|
title: "Downloads".to_string(),
|
||||||
route: ActiveSonarrBlock::Downloads.into(),
|
route: ActiveSonarrBlock::Downloads.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Blocklist",
|
title: "Blocklist".to_string(),
|
||||||
route: ActiveSonarrBlock::Blocklist.into(),
|
route: ActiveSonarrBlock::Blocklist.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History".to_string(),
|
||||||
route: ActiveSonarrBlock::History.into(),
|
route: ActiveSonarrBlock::History.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Root Folders",
|
title: "Root Folders".to_string(),
|
||||||
route: ActiveSonarrBlock::RootFolders.into(),
|
route: ActiveSonarrBlock::RootFolders.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Indexers",
|
title: "Indexers".to_string(),
|
||||||
route: ActiveSonarrBlock::Indexers.into(),
|
route: ActiveSonarrBlock::Indexers.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "System",
|
title: "System".to_string(),
|
||||||
route: ActiveSonarrBlock::System.into(),
|
route: ActiveSonarrBlock::System.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
series_info_tabs: TabState::new(vec![
|
series_info_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Seasons",
|
title: "Seasons".to_string(),
|
||||||
route: ActiveSonarrBlock::SeriesDetails.into(),
|
route: ActiveSonarrBlock::SeriesDetails.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&SERIES_DETAILS_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SERIES_DETAILS_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "History",
|
title: "History".to_string(),
|
||||||
route: ActiveSonarrBlock::SeriesHistory.into(),
|
route: ActiveSonarrBlock::SeriesHistory.into(),
|
||||||
help: String::new(),
|
help: String::new(),
|
||||||
contextual_help: Some(build_context_clue_string(&SERIES_HISTORY_CONTEXT_CLUES)),
|
contextual_help: Some(build_context_clue_string(&SERIES_HISTORY_CONTEXT_CLUES)),
|
||||||
|
config: None,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-20
@@ -15,7 +15,7 @@ use tokio::sync::{Mutex, MutexGuard};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::app::{App, ServarrConfig};
|
use crate::app::{App, ServarrConfig};
|
||||||
use crate::models::Serdeable;
|
use crate::models::{Route, Serdeable};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
@@ -206,25 +206,22 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
{
|
{
|
||||||
let app = self.app.lock().await;
|
let app = self.app.lock().await;
|
||||||
let resource = network_event.resource();
|
let resource = network_event.resource();
|
||||||
let (
|
let ServarrConfig {
|
||||||
ServarrConfig {
|
host,
|
||||||
host,
|
port,
|
||||||
port,
|
uri,
|
||||||
uri,
|
api_token,
|
||||||
api_token,
|
ssl_cert_path,
|
||||||
ssl_cert_path,
|
..
|
||||||
..
|
} = app
|
||||||
},
|
.server_tabs
|
||||||
default_port,
|
.get_active_config()
|
||||||
) = match network_event.into() {
|
.as_ref()
|
||||||
NetworkEvent::Radarr(_) => (
|
.expect("Servarr config is undefined");
|
||||||
&app.config.radarr.as_ref().expect("Radarr config undefined"),
|
let default_port = match app.get_current_route() {
|
||||||
7878,
|
Route::Radarr(_, _) => 7878,
|
||||||
),
|
Route::Sonarr(_, _) => 8989,
|
||||||
NetworkEvent::Sonarr(_) => (
|
_ => 0,
|
||||||
&app.config.sonarr.as_ref().expect("Sonarr config undefined"),
|
|
||||||
8989,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
let mut uri = if let Some(servarr_uri) = uri {
|
let mut uri = if let Some(servarr_uri) = uri {
|
||||||
format!("{servarr_uri}/api/v3{resource}")
|
format!("{servarr_uri}/api/v3{resource}")
|
||||||
|
|||||||
+2
-2
@@ -86,7 +86,7 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
.server_tabs
|
.server_tabs
|
||||||
.tabs
|
.tabs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tab| Line::from(tab.title.bold()));
|
.map(|tab| Line::from(tab.title.clone().bold()));
|
||||||
let tabs = Tabs::new(titles)
|
let tabs = Tabs::new(titles)
|
||||||
.block(borderless_block())
|
.block(borderless_block())
|
||||||
.highlight_style(Style::new().secondary())
|
.highlight_style(Style::new().secondary())
|
||||||
@@ -144,7 +144,7 @@ fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -
|
|||||||
let titles = tab_state
|
let titles = tab_state
|
||||||
.tabs
|
.tabs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tab_route| Line::from(tab_route.title.bold()));
|
.map(|tab_route| Line::from(tab_route.title.clone().bold()));
|
||||||
let tabs = Tabs::new(titles)
|
let tabs = Tabs::new(titles)
|
||||||
.block(borderless_block())
|
.block(borderless_block())
|
||||||
.highlight_style(Style::new().secondary())
|
.highlight_style(Style::new().secondary())
|
||||||
|
|||||||
+12
-8
@@ -152,17 +152,21 @@ pub(super) fn build_network_client(config: &AppConfig) -> Client {
|
|||||||
.http2_keep_alive_interval(Duration::from_secs(5))
|
.http2_keep_alive_interval(Duration::from_secs(5))
|
||||||
.tcp_keepalive(Duration::from_secs(5));
|
.tcp_keepalive(Duration::from_secs(5));
|
||||||
|
|
||||||
if let Some(radarr_config) = &config.radarr {
|
if let Some(radarr_configs) = &config.radarr {
|
||||||
if let Some(ref cert_path) = &radarr_config.ssl_cert_path {
|
for radarr_config in radarr_configs {
|
||||||
let cert = create_cert(cert_path, "Radarr");
|
if let Some(ref cert_path) = &radarr_config.ssl_cert_path {
|
||||||
client_builder = client_builder.add_root_certificate(cert);
|
let cert = create_cert(cert_path, "Radarr");
|
||||||
|
client_builder = client_builder.add_root_certificate(cert);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sonarr_config) = &config.sonarr {
|
if let Some(sonarr_configs) = &config.sonarr {
|
||||||
if let Some(ref cert_path) = &sonarr_config.ssl_cert_path {
|
for sonarr_config in sonarr_configs {
|
||||||
let cert = create_cert(cert_path, "Sonarr");
|
if let Some(ref cert_path) = &sonarr_config.ssl_cert_path {
|
||||||
client_builder = client_builder.add_root_certificate(cert);
|
let cert = create_cert(cert_path, "Sonarr");
|
||||||
|
client_builder = client_builder.add_root_certificate(cert);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user