Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a97c49a8e | |||
| 8c155ce656 | |||
|
|
5245ba6d98 | ||
|
|
f9789ecc9b | ||
| 9936ce1ab5 | |||
| 650c9783a6 | |||
| b253a389eb | |||
| 5023fbd3d1 | |||
| fdb08fbd34 | |||
| b125d3341a | |||
|
|
f73e3a4817 |
@@ -1,16 +1,14 @@
|
||||
# Adapted from https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-release-plz.yml
|
||||
# Thanks to joshka for permission to use this template!
|
||||
|
||||
name: Create Release PR and Publish Release
|
||||
name: Create major release
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
@@ -19,6 +17,12 @@ jobs:
|
||||
name: Release-plz
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -27,6 +31,8 @@ jobs:
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: major
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Adapted from https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-release-plz.yml
|
||||
# Thanks to joshka for permission to use this template!
|
||||
|
||||
name: Create minor release
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
# see https://release-plz.ieni.dev/docs/github
|
||||
# for more information
|
||||
name: Release-plz
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: minor
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Adapted from https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-release-plz.yml
|
||||
# Thanks to joshka for permission to use this template!
|
||||
|
||||
name: Create patch release
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
# see https://release-plz.ieni.dev/docs/github
|
||||
# for more information
|
||||
name: Release-plz
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
with:
|
||||
bump: patch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.1](https://github.com/Dark-Alex-17/managarr/compare/v0.2.0...v0.2.1) - 2024-11-06
|
||||
|
||||
### Other
|
||||
|
||||
- Removed the need for use_ssl to indicate SSL usage; instead just use the ssl_cert_path
|
||||
- Applied bug fix to the downloads tab as well as the context [skip ci]
|
||||
- Updated the README to not include the GitHub downloads badge since all binary releases are on crates.io [skip ci]
|
||||
- Set all releases as manually triggered instead of automatic [skip ci]
|
||||
- Updated dockerfile to no longer use the --disable-terminal-size-checks flag [skip ci]
|
||||
|
||||
## [0.1.5](https://github.com/Dark-Alex-17/managarr/compare/v0.1.4...v0.1.5) - 2024-11-03
|
||||
|
||||
### Other
|
||||
|
||||
Generated
+1
-1
@@ -1148,7 +1148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managarr"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI and CLI to manage your Servarrs"
|
||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ FROM debian:stable-slim
|
||||
# Copy the compiled binary from the builder container
|
||||
COPY --from=builder --chown=nonroot:nonroot /usr/src/managarr-temp/managarr /usr/local/bin
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/managarr", "--disable-terminal-size-checks" ]
|
||||
ENTRYPOINT [ "/usr/local/bin/managarr" ]
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://crates.io/crates/managarr)
|
||||

|
||||
[](https://codecov.io/gh/Dark-Alex-17/managarr)
|
||||
[](https://github.com/Dark-Alex-17/managarr/releases)
|
||||

|
||||
|
||||
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
|
||||
@@ -113,7 +111,7 @@ To see all available commands, simply run `managarr --help`:
|
||||
|
||||
```shell
|
||||
$ managarr --help
|
||||
managarr 0.1.5
|
||||
managarr 0.2.1
|
||||
Alex Clarke <alex.j.tusa@gmail.com>
|
||||
|
||||
A TUI and CLI to manage your Servarrs
|
||||
@@ -201,45 +199,38 @@ managarr --config /path/to/config.yml
|
||||
### Example Configuration:
|
||||
```yaml
|
||||
radarr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.78
|
||||
port: 7878
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: true
|
||||
ssl_cert_path: /path/to/radarr.crt
|
||||
ssl_cert_path: /path/to/radarr.crt # Required to enable SSL
|
||||
sonarr:
|
||||
host: 127.0.0.1
|
||||
port: 8989
|
||||
uri: http://htpc.local/sonarr # Example of using the 'uri' key instead of 'host' and 'port'
|
||||
api_token: someApiToken1234567890
|
||||
readarr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.87
|
||||
port: 8787
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
lidarr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.86
|
||||
port: 8686
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
whisparr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.69
|
||||
port: 6969
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
ssl_cert_path: /path/to/whisparr.crt
|
||||
bazarr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.67
|
||||
port: 6767
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
prowlarr:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.96
|
||||
port: 9696
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
tautulli:
|
||||
host: 127.0.0.1
|
||||
host: 192.168.0.81
|
||||
port: 8181
|
||||
api_token: someApiToken1234567890
|
||||
use_ssl: false
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::anyhow;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
|
||||
@@ -221,10 +221,10 @@ mod tests {
|
||||
fn test_radarr_config_default() {
|
||||
let radarr_config = RadarrConfig::default();
|
||||
|
||||
assert_str_eq!(radarr_config.host, "localhost");
|
||||
assert_eq!(radarr_config.host, Some("localhost".to_string()));
|
||||
assert_eq!(radarr_config.port, Some(7878));
|
||||
assert_eq!(radarr_config.uri, None);
|
||||
assert!(radarr_config.api_token.is_empty());
|
||||
assert!(!radarr_config.use_ssl);
|
||||
assert_eq!(radarr_config.ssl_cert_path, None);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-5
@@ -1,4 +1,7 @@
|
||||
use std::process;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use colored::Colorize;
|
||||
use log::{debug, error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
@@ -166,29 +169,52 @@ pub struct Data<'a> {
|
||||
pub radarr_data: RadarrData<'a>,
|
||||
}
|
||||
|
||||
pub trait ServarrConfig {
|
||||
fn validate(&self);
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
pub struct AppConfig {
|
||||
pub radarr: RadarrConfig,
|
||||
}
|
||||
|
||||
impl ServarrConfig for AppConfig {
|
||||
fn validate(&self) {
|
||||
self.radarr.validate();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct RadarrConfig {
|
||||
pub host: String,
|
||||
pub host: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub uri: Option<String>,
|
||||
pub api_token: String,
|
||||
#[serde(default)]
|
||||
pub use_ssl: bool,
|
||||
pub ssl_cert_path: Option<String>,
|
||||
}
|
||||
|
||||
impl ServarrConfig for RadarrConfig {
|
||||
fn validate(&self) {
|
||||
if self.host.is_none() && self.uri.is_none() {
|
||||
log_and_print_error("'host' or 'uri' is required for Radarr configuration".to_owned());
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RadarrConfig {
|
||||
fn default() -> Self {
|
||||
RadarrConfig {
|
||||
host: "localhost".to_string(),
|
||||
host: Some("localhost".to_string()),
|
||||
port: Some(7878),
|
||||
uri: None,
|
||||
api_token: "".to_string(),
|
||||
use_ssl: false,
|
||||
ssl_cert_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_and_print_error(error: String) {
|
||||
error!("{}", error);
|
||||
eprintln!("error: {}", error.red());
|
||||
}
|
||||
|
||||
+20
-26
@@ -10,7 +10,7 @@ use std::{io, panic, process};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Result;
|
||||
use app::AppConfig;
|
||||
use app::{log_and_print_error, AppConfig, ServarrConfig};
|
||||
use clap::{
|
||||
command, crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser,
|
||||
};
|
||||
@@ -88,6 +88,7 @@ async fn main() -> Result<()> {
|
||||
} else {
|
||||
confy::load("managarr", "config")?
|
||||
};
|
||||
config.validate();
|
||||
let reqwest_client = build_network_client(&config);
|
||||
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
|
||||
let cancellation_token = CancellationToken::new();
|
||||
@@ -229,8 +230,8 @@ fn load_config(path: &str) -> Result<AppConfig> {
|
||||
fn build_network_client(config: &AppConfig) -> Client {
|
||||
let mut client_builder = Client::builder();
|
||||
|
||||
if config.radarr.use_ssl {
|
||||
let cert = create_cert(config.radarr.ssl_cert_path.clone(), "Radarr");
|
||||
if let Some(ref cert_path) = config.radarr.ssl_cert_path {
|
||||
let cert = create_cert(cert_path, "Radarr");
|
||||
client_builder = client_builder.add_root_certificate(cert);
|
||||
}
|
||||
|
||||
@@ -244,32 +245,25 @@ fn build_network_client(config: &AppConfig) -> Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_cert(cert_path: Option<String>, servarr_name: &str) -> Certificate {
|
||||
let err = |error: String| {
|
||||
error!("{}", error);
|
||||
eprintln!("error: {}", error.red());
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
if cert_path.is_none() {
|
||||
err(format!(
|
||||
"A {} cert path is required when 'use_ssl' is 'true'",
|
||||
servarr_name
|
||||
));
|
||||
}
|
||||
|
||||
match fs::read(cert_path.unwrap()) {
|
||||
fn create_cert(cert_path: &String, servarr_name: &str) -> Certificate {
|
||||
match fs::read(cert_path) {
|
||||
Ok(cert) => match Certificate::from_pem(&cert) {
|
||||
Ok(certificate) => certificate,
|
||||
Err(_) => err(format!(
|
||||
"Unable to read the specified {} SSL certificate",
|
||||
servarr_name
|
||||
)),
|
||||
Err(_) => {
|
||||
log_and_print_error(format!(
|
||||
"Unable to read the specified {} SSL certificate",
|
||||
servarr_name
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(_) => err(format!(
|
||||
"Unable to open specified {} SSL certificate",
|
||||
servarr_name
|
||||
)),
|
||||
Err(_) => {
|
||||
log_and_print_error(format!(
|
||||
"Unable to open specified {} SSL certificate",
|
||||
servarr_name
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ mod tests {
|
||||
.with_body("{}")
|
||||
.create_async()
|
||||
.await;
|
||||
let host = server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned();
|
||||
let host = Some(server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned());
|
||||
let port = Some(
|
||||
server.host_with_port().split(':').collect::<Vec<&str>>()[1]
|
||||
.parse()
|
||||
@@ -38,8 +38,8 @@ mod tests {
|
||||
host,
|
||||
api_token: String::new(),
|
||||
port,
|
||||
use_ssl: false,
|
||||
ssl_cert_path: None,
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
app.config.radarr = radarr_config;
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
|
||||
@@ -2261,15 +2261,24 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
let RadarrConfig {
|
||||
host,
|
||||
port,
|
||||
uri,
|
||||
api_token,
|
||||
use_ssl,
|
||||
..
|
||||
ssl_cert_path,
|
||||
} = &app.config.radarr;
|
||||
let protocol = if *use_ssl { "https" } else { "http" };
|
||||
let uri = format!(
|
||||
"{protocol}://{host}:{}/api/v3{resource}",
|
||||
port.unwrap_or(7878)
|
||||
);
|
||||
let uri = if let Some(radarr_uri) = uri {
|
||||
format!("{radarr_uri}/api/v3{resource}")
|
||||
} else {
|
||||
let protocol = if ssl_cert_path.is_some() {
|
||||
"https"
|
||||
} else {
|
||||
"http"
|
||||
};
|
||||
let host = host.as_ref().unwrap();
|
||||
format!(
|
||||
"{protocol}://{host}:{}/api/v3{resource}",
|
||||
port.unwrap_or(7878)
|
||||
)
|
||||
};
|
||||
|
||||
RequestProps {
|
||||
uri,
|
||||
|
||||
@@ -794,7 +794,7 @@ mod test {
|
||||
.match_header("X-Api-Key", "test1234");
|
||||
async_server = async_server.expect_at_most(0).create_async().await;
|
||||
|
||||
let host = server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned();
|
||||
let host = Some(server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned());
|
||||
let port = Some(
|
||||
server.host_with_port().split(':').collect::<Vec<&str>>()[1]
|
||||
.parse()
|
||||
@@ -805,8 +805,7 @@ mod test {
|
||||
host,
|
||||
port,
|
||||
api_token: "test1234".to_owned(),
|
||||
use_ssl: false,
|
||||
ssl_cert_path: None,
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
app.config.radarr = radarr_config;
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
@@ -4876,11 +4875,10 @@ mod test {
|
||||
assert!(request_props.api_token.is_empty());
|
||||
|
||||
app_arc.lock().await.config.radarr = RadarrConfig {
|
||||
host: "192.168.0.123".to_owned(),
|
||||
host: Some("192.168.0.123".to_owned()),
|
||||
port: Some(8080),
|
||||
api_token: "testToken1234".to_owned(),
|
||||
use_ssl: false,
|
||||
ssl_cert_path: None,
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4889,11 +4887,33 @@ mod test {
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
app_arc.lock().await.config.radarr = RadarrConfig {
|
||||
host: "192.168.0.123".to_owned(),
|
||||
host: Some("192.168.0.123".to_owned()),
|
||||
port: Some(8080),
|
||||
api_token: api_token.clone(),
|
||||
use_ssl: true,
|
||||
ssl_cert_path: None,
|
||||
ssl_cert_path: Some("/test/cert.crt".to_owned()),
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let request_props = network
|
||||
.radarr_request_props_from("/test", RequestMethod::Get, None::<()>)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(request_props.uri, "https://192.168.0.123:8080/api/v3/test");
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert_str_eq!(request_props.api_token, api_token);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_radarr_request_props_from_custom_radarr_config_using_uri_instead_of_host_and_port()
|
||||
{
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
app_arc.lock().await.config.radarr = RadarrConfig {
|
||||
uri: Some("https://192.168.0.123:8080".to_owned()),
|
||||
api_token: api_token.clone(),
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
@@ -4986,7 +5006,7 @@ mod test {
|
||||
|
||||
async_server = async_server.create_async().await;
|
||||
|
||||
let host = server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned();
|
||||
let host = Some(server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned());
|
||||
let port = Some(
|
||||
server.host_with_port().split(':').collect::<Vec<&str>>()[1]
|
||||
.parse()
|
||||
@@ -4997,8 +5017,7 @@ mod test {
|
||||
host,
|
||||
port,
|
||||
api_token: "test1234".to_owned(),
|
||||
use_ssl: false,
|
||||
ssl_cert_path: None,
|
||||
..RadarrConfig::default()
|
||||
};
|
||||
app.config.radarr = radarr_config;
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
|
||||
@@ -92,7 +92,11 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
);
|
||||
}
|
||||
|
||||
let percent = 1f64 - (*sizeleft as f64 / *size as f64);
|
||||
let percent = if *size == 0 {
|
||||
0.0
|
||||
} else {
|
||||
1f64 - (*sizeleft as f64 / *size as f64)
|
||||
};
|
||||
let file_size: f64 = convert_to_gb(*size);
|
||||
|
||||
Row::new(vec![
|
||||
|
||||
Reference in New Issue
Block a user