Implements multi account switching in the gui.

This commit is contained in:
2026-08-23 23:43:23 -07:00
parent 30cdc61ae0
commit f9eaf0f215
2 changed files with 65 additions and 13 deletions

View File

@@ -70,7 +70,6 @@ This document defines the complete project roadmap and task tracking system for
| ID | Title | Status | Type |
|---|---|---|---|
| [NUT-017](#nut-017) | Implement Multi-Account Switching (GUI + CLI) | Triage | Integration |
| [NUT-018](#nut-018) | Implement Packaging for macOS, Windows, Linux | Triage | Chore |
| [NUT-019](#nut-019) | Implement Homebrew/Winget/Chocolatey Manifests | Triage | Chore |
| [NUT-020](#nut-020) | Write Documentation + Examples | Triage | Chore |
@@ -93,6 +92,7 @@ This document defines the complete project roadmap and task tracking system for
| [NUT-014](#nut-014) | Implement GUI Credential Management UI | Fixed | Feature |
| [NUT-015](#nut-015) | Implement GUI Upload Progress Bars | Fixed | Feature |
| [NUT-016](#nut-016) | Implement Shared Auth Token Reuse | Fixed | Integration |
| [NUT-017](#nut-017) | Implement Multi-Account Switching (GUI + CLI) | Fixed | Integration |
| [NUT-021](#nut-021) | Support Headless & SSH Remote Authentication Modes | Fixed | Feature |
---
@@ -422,19 +422,19 @@ Ensure both CLI and GUI reuse the same credential store and cached tokens.
- NUT-012
<a id="nut-017" class="task" data-status="triage" data-task-type="integration"></a>
<a id="nut-017" class="task" data-status="done" data-task-type="integration"></a>
### Implement Multi-Account Switching (GUI + CLI)
**ID:** NUT-017
**Status:** Triage
**Status:** Fixed
**Type:** Integration
**Description:**
Add multi-account switching to both CLI and GUI.
**Requirements:**
- [ ] CLI `--account` flag
- [ ] GUI dropdown
- [ ] Shared backend logic
- [x] CLI `--account` flag
- [x] GUI dropdown
- [x] Shared backend logic
**Dependencies:**
- NUT-007

View File

@@ -12,7 +12,7 @@ use nextcloud_client::{
ProgressCallback, ProgressEvent, Result, UploadOptions,
};
#[derive(Parser, Debug)]
#[derive(Parser, Debug, PartialEq)]
#[command(
name = "nut",
author = "Tom Hicks <headhunter3@gmail.com>",
@@ -25,7 +25,7 @@ struct Cli {
command: Commands,
}
#[derive(Subcommand, Debug)]
#[derive(Subcommand, Debug, PartialEq)]
enum Commands {
/// Log in to a Nextcloud server using browser authorization (Login Flow v2) or credentials
Login(LoginArgs),
@@ -41,7 +41,7 @@ enum Commands {
Accounts(AccountListArgs),
}
#[derive(Args, Debug)]
#[derive(Args, Debug, PartialEq)]
struct LoginArgs {
/// Base URL of the Nextcloud instance (e.g. https://cloud.example.com)
server_url: String,
@@ -71,7 +71,7 @@ struct LoginArgs {
app_password: Option<String>,
}
#[derive(Args, Debug, Clone, Default)]
#[derive(Args, Debug, Clone, Default, PartialEq)]
struct OutputFormatArgs {
/// Format output as JSON
#[arg(long, conflicts_with_all = ["tsv", "url_only", "direct_url_only"])]
@@ -100,7 +100,7 @@ impl OutputFormatArgs {
}
}
#[derive(Args, Debug)]
#[derive(Args, Debug, PartialEq)]
struct UploadArgs {
/// Path to local file(s) or directories to upload
#[arg(value_name = "FILE")]
@@ -150,7 +150,7 @@ struct UploadArgs {
format: OutputFormatArgs,
}
#[derive(Subcommand, Debug)]
#[derive(Subcommand, Debug, PartialEq)]
enum AccountCommands {
/// List all configured Nextcloud accounts
List(AccountListArgs),
@@ -168,7 +168,7 @@ enum AccountCommands {
},
}
#[derive(Args, Debug, Clone, Default)]
#[derive(Args, Debug, Clone, Default, PartialEq)]
struct AccountListArgs {
/// Format output as JSON
#[arg(long, conflicts_with = "tsv")]
@@ -771,3 +771,55 @@ fn handle_account_delete(account: &str) -> Result<()> {
println!("\x1b[1;32m✓\x1b[0m Account '\x1b[1m{}\x1b[0m' deleted.", account);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_account_flag_parsing() {
let parsed = Cli::try_parse_from(&[
"nut",
"upload",
"--account",
"alice@cloud.example.com",
"document.pdf",
])
.unwrap();
match parsed.command {
Commands::Upload(args) => {
assert_eq!(args.account.as_deref(), Some("alice@cloud.example.com"));
assert_eq!(args.files, vec![PathBuf::from("document.pdf")]);
}
_ => panic!("Expected Upload command"),
}
}
#[test]
fn test_cli_account_subcommands_parsing() {
let parsed = Cli::try_parse_from(&["nut", "account", "default", "Work"]).unwrap();
match parsed.command {
Commands::Account(AccountCommands::Default { account }) => {
assert_eq!(account, "Work");
}
_ => panic!("Expected Account Default command"),
}
let parsed_del = Cli::try_parse_from(&["nut", "account", "delete", "bob@cloud.com"]).unwrap();
match parsed_del.command {
Commands::Account(AccountCommands::Delete { account }) => {
assert_eq!(account, "bob@cloud.com");
}
_ => panic!("Expected Account Delete command"),
}
let parsed_list = Cli::try_parse_from(&["nut", "accounts", "--json"]).unwrap();
match parsed_list.command {
Commands::Accounts(args) => {
assert!(args.json);
}
_ => panic!("Expected Accounts list command"),
}
}
}