Compare commits
7 Commits
6411fecb4f
...
phase-3-gu
| Author | SHA1 | Date | |
|---|---|---|---|
| 75ec29ea86 | |||
| 8a7a201daa | |||
| 5b29784c07 | |||
| 2b6f379273 | |||
| f9eaf0f215 | |||
| 30cdc61ae0 | |||
| a4838bf8d9 |
47
.github/workflows/ci.yml
vendored
Normal file
47
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "phase-*" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Rust & Workspace Tests (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install GUI frontend dependencies
|
||||
working-directory: gui
|
||||
run: npm ci
|
||||
|
||||
- name: Build GUI frontend
|
||||
working-directory: gui
|
||||
run: npm run build
|
||||
|
||||
- name: Run Workspace Tests
|
||||
run: cargo test --workspace --verbose
|
||||
168
.github/workflows/release.yml
vendored
Normal file
168
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-cli:
|
||||
name: Build CLI (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
binary_name: nut
|
||||
artifact_name: nut-aarch64-apple-darwin.tar.gz
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
binary_name: nut
|
||||
artifact_name: nut-x86_64-apple-darwin.tar.gz
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
binary_name: nut
|
||||
artifact_name: nut-x86_64-unknown-linux-musl.tar.gz
|
||||
use_cross: true
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
binary_name: nut
|
||||
artifact_name: nut-aarch64-unknown-linux-musl.tar.gz
|
||||
use_cross: true
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
binary_name: nut.exe
|
||||
artifact_name: nut-x86_64-pc-windows-msvc.zip
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install cross
|
||||
if: matrix.use_cross
|
||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||
|
||||
- name: Build static CLI binary (with cross)
|
||||
if: matrix.use_cross
|
||||
run: cross build --release --target ${{ matrix.target }} -p nut
|
||||
|
||||
- name: Build CLI binary (standard)
|
||||
if: "!matrix.use_cross"
|
||||
run: cargo build --release --target ${{ matrix.target }} -p nut
|
||||
|
||||
- name: Package Unix binary
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
mkdir -p staging
|
||||
cp target/${{ matrix.target }}/release/${{ matrix.binary_name }} staging/
|
||||
cp README.md LICENSE staging/
|
||||
tar -czf ${{ matrix.artifact_name }} -C staging .
|
||||
|
||||
- name: Package Windows binary
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item target/${{ matrix.target }}/release/${{ matrix.binary_name }} staging/
|
||||
Copy-Item README.md, LICENSE staging/
|
||||
Compress-Archive -Path staging/* -DestinationPath ${{ matrix.artifact_name }}
|
||||
|
||||
- name: Upload CLI Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.artifact_name }}
|
||||
|
||||
build-gui:
|
||||
name: Build GUI (${{ matrix.platform }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: macOS
|
||||
os: macos-latest
|
||||
- platform: Linux
|
||||
os: ubuntu-latest
|
||||
- platform: Windows
|
||||
os: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev rpm
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: gui
|
||||
run: npm ci
|
||||
|
||||
- name: Build Tauri GUI
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
projectPath: "./gui"
|
||||
tagName: ${{ github.ref_name }}
|
||||
releaseName: "Nextcloud Upload Tool ${{ github.ref_name }}"
|
||||
releaseBody: "See release notes for changes."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
|
||||
publish-release:
|
||||
name: Publish Release & Checksums
|
||||
needs: [build-cli, build-gui]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all CLI artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Generate Checksums
|
||||
run: |
|
||||
cd artifacts
|
||||
find . -type f -name "*.tar.gz" -o -name "*.zip" -exec sha256sum {} + > SHA256SUMS
|
||||
cat SHA256SUMS
|
||||
|
||||
- name: Upload Checksums to GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
artifacts/**/*
|
||||
artifacts/SHA256SUMS
|
||||
draft: false
|
||||
prerelease: false
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
/target/
|
||||
**/target/
|
||||
**/*.rs.bk
|
||||
dist-packages/
|
||||
|
||||
# Cargo local configs
|
||||
.cargo/config.toml.local
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -558,6 +558,15 @@ dependencies = [
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
@@ -2492,6 +2501,7 @@ name = "nut"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"indicatif",
|
||||
"nextcloud_client",
|
||||
"open",
|
||||
|
||||
135
README.md
135
README.md
@@ -1,68 +1,113 @@
|
||||
# Nextcloud Upload Tool (`nut`)
|
||||
|
||||
[](https://github.com/majinnaibu/nextcloud-upload-tool/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
|
||||
A fast, cross-platform CLI tool and desktop GUI application for uploading files to Nextcloud and instantly generating direct download / public share links.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
## Key Features
|
||||
|
||||
**Nextcloud Upload Tool** is designed for quick terminal workflows and smooth desktop integration. It allows you to pipe or upload files directly to your Nextcloud instance, securely manage credentials via your OS keychain, and immediately copy shareable links.
|
||||
|
||||
### Key Features
|
||||
|
||||
- ⚡ **High-Performance Uploads**: WebDAV streaming uploads with real-time progress reporting and pipe support (`stdin`, `pv`).
|
||||
- 🔗 **Instant Share Links**: Direct integration with Nextcloud's OCS Sharing API to generate public share URLs and direct-download links.
|
||||
- ⚡ **High-Performance Uploads**: WebDAV streaming uploads with real-time progress reporting and pipe support (`stdin`, `pv`, Unix pipelines).
|
||||
- 🔗 **Instant Share Links**: Direct integration with Nextcloud's OCS Sharing API to generate public share URLs and direct-download links with optional password protection.
|
||||
- 🔐 **Secure Credential Storage**: Native OS Keychain integration (macOS Keychain, Windows Credential Manager, Linux Secret Service) with encrypted file fallback.
|
||||
- 👥 **Multi-Account Support**: Configure and switch between multiple Nextcloud accounts or self-hosted instances seamlessly.
|
||||
- 🖥️ **CLI & GUI Frontends**: A lightweight terminal binary (`nut`) and a modern desktop application powered by Tauri.
|
||||
- 👥 **Multi-Account Support**: Configure and switch between multiple Nextcloud accounts or self-hosted instances seamlessly across CLI and GUI.
|
||||
- 🖥️ **Modern Desktop GUI**: Drag-and-drop queue management, per-file & total progress bars, retry handling, and browser-based Login Flow v2.
|
||||
- 🤖 **Automation Ready**: Output formatting options (`--json`, `--tsv`, `--url-only`, `--direct-url-only`) and headless SSH / CI authentication.
|
||||
- 🐚 **Shell Completions**: First-class completion scripts for `bash`, `zsh`, `fish`, `powershell`, and `elvish`.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- 📖 **[CLI Reference](docs/cli-reference.md)**: Full syntax, subcommands (`login`, `upload`, `accounts`, `completions`), and options reference.
|
||||
- 💡 **[Automation & Scripting Recipes](docs/automation-recipes.md)**: Real-world examples for pipelines (`stdin`, `pv`, `mysqldump`, `curl`), `jq` parsing, and CI/CD workflows.
|
||||
- 📦 **[Installation Guide](docs/installation.md)**: Pre-built binaries, packages (`.dmg`, `.deb`, `.rpm`, `.msi`, `.AppImage`), and source compilation instructions.
|
||||
- 🖥️ **[GUI User Guide](docs/gui-guide.md)**: Walkthrough of the desktop app, drag-and-drop queue, retry mechanics, and account management.
|
||||
- 👥 **[Multi-Account Guide](docs/multi-account.md)**: Managing multiple servers, switching active defaults, and CLI/GUI token sharing.
|
||||
- 🏗️ **[Architecture & Developer Guide](docs/architecture.md)**: Workspace layout, WebDAV/OCS protocol implementation, and Tauri IPC bridge.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. CLI Usage
|
||||
|
||||
#### Connect your Nextcloud account:
|
||||
```bash
|
||||
# Interactive Login Flow v2 (opens browser):
|
||||
nut login https://cloud.example.com --label "Work"
|
||||
|
||||
# Headless SSH authorization (prints URL in terminal):
|
||||
nut login https://cloud.example.com --no-browser --label "Server"
|
||||
|
||||
# Or connect interactively via terminal prompt:
|
||||
nut login https://cloud.example.com --manual
|
||||
|
||||
# Or non-interactive / CI automated script:
|
||||
nut login https://cloud.example.com -u alice -p "app-password"
|
||||
```
|
||||
|
||||
#### Upload files and generate share links:
|
||||
```bash
|
||||
# Upload a single file and generate a public share link:
|
||||
nut upload -s document.pdf
|
||||
|
||||
# Upload multiple files into a remote folder:
|
||||
nut upload -s -d "Projects/2026" file1.png file2.png
|
||||
|
||||
# Upload recursively:
|
||||
nut upload -s -r assets/
|
||||
|
||||
# Stream from standard input (e.g. backup pipes):
|
||||
tar -czf - data/ | nut upload -s --stdin --filename "backup.tar.gz"
|
||||
|
||||
# Output only the share link (ideal for scripts and clipboard pipes):
|
||||
nut upload -s --url-only report.pdf | pbcopy
|
||||
```
|
||||
|
||||
#### Shell Completions:
|
||||
```bash
|
||||
# Generate shell completions (e.g. Zsh)
|
||||
nut completions zsh > ~/.zfunc/_nut
|
||||
```
|
||||
|
||||
#### Manage multiple accounts:
|
||||
```bash
|
||||
# List all accounts:
|
||||
nut accounts
|
||||
|
||||
# Switch default active account:
|
||||
nut account default "Work"
|
||||
|
||||
# Upload to a specific account:
|
||||
nut upload --account "Personal" photo.jpg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. GUI Usage
|
||||
|
||||
Launch the desktop app via `nut-gui` or from your system applications menu. Drag and drop any files into the window, configure your remote destination folder, toggle public share link creation, and click **Upload All**.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
This repository is structured as a Cargo workspace:
|
||||
|
||||
```
|
||||
.
|
||||
├── nextcloud_client/ # Core Rust backend library (WebDAV, OCS API, Keyring, Config)
|
||||
├── cli/ # Terminal CLI executable (`nut`)
|
||||
├── gui/ # Desktop GUI application (Tauri + Web frontend)
|
||||
├── Tasks.md # Canonical project roadmap and task tracking
|
||||
├── nextcloud_client/ # Core Rust backend library (WebDAV, OCS API, Keyring, Config)
|
||||
├── cli/ # Terminal CLI executable (`nut`)
|
||||
├── gui/ # Desktop GUI application (Tauri v2 + React frontend)
|
||||
├── docs/ # Detailed guides and developer documentation
|
||||
├── scripts/ # Packaging and build helper scripts
|
||||
├── Tasks.md # Canonical project roadmap and task tracking
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Rust](https://www.rust-lang.org/) (1.75+ recommended)
|
||||
- A running [Nextcloud](https://nextcloud.com/) instance with WebDAV and sharing enabled
|
||||
|
||||
### Building the Workspace
|
||||
|
||||
Clone the repository and build the workspace crates:
|
||||
|
||||
```bash
|
||||
# Build all workspace members
|
||||
cargo build
|
||||
|
||||
# Run the CLI tool
|
||||
cargo run -p nut -- --help
|
||||
|
||||
# Run tests
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap & Tasks
|
||||
|
||||
Project tasks, feature specifications, and current progress are tracked in [Tasks.md](Tasks.md).
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](LICENSE).
|
||||
|
||||
112
Tasks.md
112
Tasks.md
@@ -66,19 +66,11 @@ This document defines the complete project roadmap and task tracking system for
|
||||
---
|
||||
|
||||
<a id="tasks-summary"></a>
|
||||
## Tasks Summary (Rendered from task details)
|
||||
## Tasks Summary (Rendered from task-details)
|
||||
|
||||
| ID | Title | Status | Type |
|
||||
|---|---|---|---|
|
||||
| [NUT-014](#nut-014) | Implement GUI Credential Management UI | Triage | Feature |
|
||||
| [NUT-015](#nut-015) | Implement GUI Upload Progress Bars | Triage | Feature |
|
||||
| [NUT-016](#nut-016) | Implement Shared Auth Token Reuse | Triage | Integration |
|
||||
| [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 |
|
||||
| [NUT-022](#nut-022) | Implement CLI Shell Completions Generation | Triage | Feature |
|
||||
| [NUT-023](#nut-023) | Write Comprehensive CLI Documentation and Automation Guides | Triage | Chore |
|
||||
| [NUT-024](#nut-024) | Create Docker/Podman Nextcloud Integration Test Harness | Triage | Foundation |
|
||||
| [NUT-001](#nut-001) | Establish Repository Structure | Fixed | Foundation |
|
||||
| [NUT-002](#nut-002) | Implement Shared Rust Backend Library | Fixed | Foundation |
|
||||
@@ -93,7 +85,15 @@ This document defines the complete project roadmap and task tracking system for
|
||||
| [NUT-011](#nut-011) | Implement CLI Progress Reporting + pv Support | Fixed | Feature |
|
||||
| [NUT-012](#nut-012) | Implement GUI (Tauri) Frontend | Fixed | Feature |
|
||||
| [NUT-013](#nut-013) | Implement GUI File Queue + Drag-and-Drop | Fixed | Feature |
|
||||
| [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-018](#nut-018) | Implement Packaging for macOS, Windows, Linux | Fixed | Chore |
|
||||
| [NUT-020](#nut-020) | Write Documentation + Examples | Fixed | Chore |
|
||||
| [NUT-021](#nut-021) | Support Headless & SSH Remote Authentication Modes | Fixed | Feature |
|
||||
| [NUT-022](#nut-022) | Implement CLI Shell Completions Generation | Fixed | Feature |
|
||||
| [NUT-023](#nut-023) | Write Comprehensive CLI Documentation and Automation Guides | Fixed | Chore |
|
||||
|
||||
---
|
||||
|
||||
@@ -361,40 +361,40 @@ Add drag-and-drop file support and a queue system for multiple uploads.
|
||||
- NUT-012
|
||||
|
||||
|
||||
<a id="nut-014" class="task" data-status="triage" data-task-type="feature"></a>
|
||||
<a id="nut-014" class="task" data-status="done" data-task-type="feature"></a>
|
||||
### Implement GUI Credential Management UI
|
||||
**ID:** NUT-014
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Feature
|
||||
|
||||
**Description:**
|
||||
Add UI for managing accounts, logging in (including one-click browser authorization via Login Flow v2), logging out, and switching accounts.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] Account list UI
|
||||
- [ ] Browser-based login button (Login Flow v2)
|
||||
- [ ] Manual login form (server/user/token)
|
||||
- [ ] Logout button
|
||||
- [ ] Switch account dropdown
|
||||
- [x] Account list UI
|
||||
- [x] Browser-based login button (Login Flow v2)
|
||||
- [x] Manual login form (server/user/token)
|
||||
- [x] Logout button
|
||||
- [x] Switch account dropdown
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-007
|
||||
- NUT-012
|
||||
|
||||
|
||||
<a id="nut-015" class="task" data-status="triage" data-task-type="feature"></a>
|
||||
<a id="nut-015" class="task" data-status="done" data-task-type="feature"></a>
|
||||
### Implement GUI Upload Progress Bars
|
||||
**ID:** NUT-015
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Feature
|
||||
|
||||
**Description:**
|
||||
Add per-file and total progress bars to the GUI.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] Per-file progress
|
||||
- [ ] Total progress
|
||||
- [ ] Error display
|
||||
- [x] Per-file progress
|
||||
- [x] Total progress
|
||||
- [x] Error display
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-003
|
||||
@@ -402,19 +402,19 @@ Add per-file and total progress bars to the GUI.
|
||||
- NUT-013
|
||||
|
||||
|
||||
<a id="nut-016" class="task" data-status="triage" data-task-type="integration"></a>
|
||||
<a id="nut-016" class="task" data-status="done" data-task-type="integration"></a>
|
||||
### Implement Shared Auth Token Reuse
|
||||
**ID:** NUT-016
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Integration
|
||||
|
||||
**Description:**
|
||||
Ensure both CLI and GUI reuse the same credential store and cached tokens.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] Shared credential backend
|
||||
- [ ] Shared token cache
|
||||
- [ ] Unified config format
|
||||
- [x] Shared credential backend
|
||||
- [x] Shared token cache
|
||||
- [x] Unified config format
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-006
|
||||
@@ -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
|
||||
@@ -442,20 +442,20 @@ Add multi-account switching to both CLI and GUI.
|
||||
- NUT-014
|
||||
|
||||
|
||||
<a id="nut-018" class="task" data-status="triage" data-task-type="chore"></a>
|
||||
<a id="nut-018" class="task" data-status="done" data-task-type="chore"></a>
|
||||
### Implement Packaging for macOS, Windows, Linux
|
||||
**ID:** NUT-018
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Chore
|
||||
|
||||
**Description:**
|
||||
Package the CLI and GUI for distribution.
|
||||
Package the CLI and GUI for distribution across macOS, Windows, and Linux.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] macOS `.app` + `.dmg`
|
||||
- [ ] Windows `.exe` + installer
|
||||
- [ ] Linux `.deb` + `.rpm`
|
||||
- [ ] Static CLI binaries
|
||||
- [x] macOS `.app` + `.dmg`
|
||||
- [x] Windows `.exe` + installer
|
||||
- [x] Linux `.deb` + `.rpm`
|
||||
- [x] Static CLI binaries
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-008
|
||||
@@ -481,20 +481,20 @@ Add package manager manifests for easy installation.
|
||||
- NUT-018
|
||||
|
||||
|
||||
<a id="nut-020" class="task" data-status="triage" data-task-type="chore"></a>
|
||||
<a id="nut-020" class="task" data-status="done" data-task-type="chore"></a>
|
||||
### Write Documentation + Examples
|
||||
**ID:** NUT-020
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Chore
|
||||
|
||||
**Description:**
|
||||
Write comprehensive project documentation covering installation, GUI usage, multi-account setup, and overall project architecture.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] GUI overview and visual walkthrough
|
||||
- [ ] Cross-platform installation instructions
|
||||
- [ ] Multi-account management guide
|
||||
- [ ] Architecture and developer setup documentation
|
||||
- [x] GUI overview and visual walkthrough
|
||||
- [x] Cross-platform installation instructions
|
||||
- [x] Multi-account management guide
|
||||
- [x] Architecture and developer setup documentation
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-012
|
||||
@@ -522,39 +522,39 @@ Ensure smooth authentication experiences when running the CLI over SSH or in hea
|
||||
- NUT-008
|
||||
|
||||
|
||||
<a id="nut-022" class="task" data-status="triage" data-task-type="feature"></a>
|
||||
<a id="nut-022" class="task" data-status="done" data-task-type="feature"></a>
|
||||
### Implement CLI Shell Completions Generation
|
||||
**ID:** NUT-022
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Feature
|
||||
|
||||
**Description:**
|
||||
Add automated shell completion script generation using `clap_complete` for major shells (`bash`, `zsh`, `fish`, `powershell`, `elvish`).
|
||||
|
||||
**Requirements:**
|
||||
- [ ] Add `clap_complete` crate dependency
|
||||
- [ ] Implement `nut completions <SHELL>` subcommand
|
||||
- [ ] Support `bash`, `zsh`, `fish`, `powershell`, and `elvish` output to stdout
|
||||
- [ ] Include quick installation instructions in command help
|
||||
- [x] Add `clap_complete` crate dependency
|
||||
- [x] Implement `nut completions <SHELL>` subcommand
|
||||
- [x] Support `bash`, `zsh`, `fish`, `powershell`, and `elvish` output to stdout
|
||||
- [x] Include quick installation instructions in command help
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-008
|
||||
|
||||
|
||||
<a id="nut-023" class="task" data-status="triage" data-task-type="chore"></a>
|
||||
<a id="nut-023" class="task" data-status="done" data-task-type="chore"></a>
|
||||
### Write Comprehensive CLI Documentation and Automation Guides
|
||||
**ID:** NUT-023
|
||||
**Status:** Triage
|
||||
**Status:** Fixed
|
||||
**Type:** Chore
|
||||
|
||||
**Description:**
|
||||
Write dedicated CLI reference documentation and practical automation guides for scripting, CI/CD, and Unix pipeline workflows.
|
||||
|
||||
**Requirements:**
|
||||
- [ ] Document all CLI subcommands (`login`, `upload`, `accounts`, `completions`) and flags in `README.md`
|
||||
- [ ] Provide practical recipes for piping data (`stdin`, `pv`, `curl`, `mysqldump`)
|
||||
- [ ] Provide scripting examples parsing `--json`, `--tsv`, `--url-only`, and `--direct-url-only` with `jq` and `xargs`
|
||||
- [ ] Document headless SSH and CI/CD automated provisioning with `--username` and `--app-password`
|
||||
- [x] Document all CLI subcommands (`login`, `upload`, `accounts`, `completions`) and flags in `README.md`
|
||||
- [x] Provide practical recipes for piping data (`stdin`, `pv`, `curl`, `mysqldump`)
|
||||
- [x] Provide scripting examples parsing `--json`, `--tsv`, `--url-only`, and `--direct-url-only` with `jq` and `xargs`
|
||||
- [x] Document headless SSH and CI/CD automated provisioning with `--username` and `--app-password`
|
||||
|
||||
**Dependencies:**
|
||||
- NUT-008
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
nextcloud_client = { path = "../nextcloud_client" }
|
||||
clap = { version = "4.5", features = ["derive", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "default-tls"] }
|
||||
open = "5.3"
|
||||
|
||||
117
cli/src/main.rs
117
cli/src/main.rs
@@ -1,4 +1,5 @@
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
use clap_complete::{generate, Shell};
|
||||
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
|
||||
use serde::Serialize;
|
||||
use std::fs;
|
||||
@@ -12,7 +13,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 +26,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),
|
||||
@@ -39,9 +40,16 @@ enum Commands {
|
||||
|
||||
/// Quick alias to list all configured accounts
|
||||
Accounts(AccountListArgs),
|
||||
|
||||
/// Generate shell completion scripts for bash, zsh, fish, powershell, or elvish
|
||||
Completions {
|
||||
/// Target shell to generate completions for
|
||||
#[arg(value_enum)]
|
||||
shell: Shell,
|
||||
},
|
||||
}
|
||||
|
||||
#[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 +79,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 +108,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 +158,7 @@ struct UploadArgs {
|
||||
format: OutputFormatArgs,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
#[derive(Subcommand, Debug, PartialEq)]
|
||||
enum AccountCommands {
|
||||
/// List all configured Nextcloud accounts
|
||||
List(AccountListArgs),
|
||||
@@ -168,7 +176,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")]
|
||||
@@ -207,6 +215,10 @@ async fn main() {
|
||||
handle_account_set_default(&account)
|
||||
}
|
||||
Commands::Account(AccountCommands::Delete { account }) => handle_account_delete(&account),
|
||||
Commands::Completions { shell } => {
|
||||
handle_completions(shell);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
@@ -215,6 +227,12 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate shell completion scripts for supported shells.
|
||||
fn handle_completions(shell: Shell) {
|
||||
let mut cmd = Cli::command();
|
||||
generate(shell, &mut cmd, "nut", &mut io::stdout());
|
||||
}
|
||||
|
||||
/// Handle interactive or non-interactive login flows.
|
||||
async fn handle_login(args: LoginArgs) -> Result<()> {
|
||||
let server_url = ClientConfig::normalize_url(&args.server_url)?;
|
||||
@@ -771,3 +789,86 @@ 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"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_completions_parsing() {
|
||||
let shells = [
|
||||
("bash", Shell::Bash),
|
||||
("zsh", Shell::Zsh),
|
||||
("fish", Shell::Fish),
|
||||
("powershell", Shell::PowerShell),
|
||||
("elvish", Shell::Elvish),
|
||||
];
|
||||
|
||||
for (name, expected_shell) in shells {
|
||||
let parsed = Cli::try_parse_from(&["nut", "completions", name]).unwrap();
|
||||
match parsed.command {
|
||||
Commands::Completions { shell } => {
|
||||
assert_eq!(shell, expected_shell);
|
||||
}
|
||||
_ => panic!("Expected Completions command for shell {name}"),
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut cmd = Cli::command();
|
||||
generate(expected_shell, &mut cmd, "nut", &mut buf);
|
||||
assert!(!buf.is_empty(), "Generated completion for {name} should not be empty");
|
||||
let output_str = String::from_utf8(buf).expect("Completions should be valid UTF-8");
|
||||
assert!(
|
||||
output_str.contains("nut"),
|
||||
"Completion output should contain 'nut'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
92
docs/architecture.md
Normal file
92
docs/architecture.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Project Architecture & Developer Setup
|
||||
|
||||
This document describes the architectural layout, internal protocols, security model, and developer setup for the **Nextcloud Upload Tool**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Workspace Layout
|
||||
|
||||
The repository is organized as a Cargo workspace with three primary crates:
|
||||
|
||||
```
|
||||
nextcloud-upload-tool/
|
||||
├── nextcloud_client/ # Shared Rust core library
|
||||
│ ├── src/
|
||||
│ │ ├── auth.rs # Nextcloud Login Flow v2 client & polling
|
||||
│ │ ├── client.rs # NextcloudClient WebDAV client & status checks
|
||||
│ │ ├── config.rs # ClientConfig and URL normalization
|
||||
│ │ ├── credentials.rs # CredentialStore (OS Keychain + accounts.json)
|
||||
│ │ ├── download.rs # Direct-download URL generator
|
||||
│ │ ├── progress.rs # Async ProgressStream & progress callbacks
|
||||
│ │ └── sharing.rs # OCS Sharing API (public share link creation)
|
||||
├── cli/ # CLI binary (`nut`)
|
||||
│ └── src/
|
||||
│ └── main.rs # Clap CLI parser, upload handlers, terminal output
|
||||
├── gui/ # Desktop application (Tauri v2 + React)
|
||||
│ ├── src/ # React + TypeScript frontend
|
||||
│ │ ├── App.tsx # UI tabs, drag-and-drop, state management
|
||||
│ │ └── App.css # Styling and responsive layout
|
||||
│ └── src-tauri/ # Tauri Rust backend
|
||||
│ ├── src/commands.rs # IPC command handlers forwarding to nextcloud_client
|
||||
│ └── tauri.conf.json # Multi-platform bundle configuration
|
||||
└── docs/ # Documentation & user guides
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Shared Core Architecture (`nextcloud_client`)
|
||||
|
||||
Both the CLI and GUI frontends share the `nextcloud_client` crate to ensure consistent behavior:
|
||||
|
||||
- **WebDAV Upload Pipeline**:
|
||||
- Implements HTTP PUT requests against Nextcloud's WebDAV endpoint (`/remote.php/dav/files/<user>/<path>`).
|
||||
- Employs streaming request bodies wrapped in a custom `ProgressStream` to capture transferred byte counts in real time without buffering large files in RAM.
|
||||
- Automatically handles intermediate remote directory resolution.
|
||||
|
||||
- **OCS Sharing API**:
|
||||
- Interacts with `/ocs/v2.php/apps/files_sharing/api/v1/shares` using OCS headers (`OCS-APIRequest: true`).
|
||||
- Creates public read-only shares (`shareType=3`, `permissions=1`) with optional password protection.
|
||||
- Generates canonical public share links (`/index.php/s/<token>`) and direct download links (`/index.php/s/<token>/download`).
|
||||
|
||||
- **Unified Credential Storage**:
|
||||
- **Keyring Service**: Keyring service identifier `me.majinnaibu.nut`.
|
||||
- **Account Metadata**: Stored in `~/.config/nut/accounts.json` containing account IDs, server URLs, usernames, labels, and default account flags.
|
||||
- **Secrets**: App passwords and tokens are stored in the OS native keychain (Apple Keychain on macOS, Windows Credential Manager on Windows, and Secret Service on Linux).
|
||||
|
||||
---
|
||||
|
||||
## 3. GUI Architecture & IPC Bridge
|
||||
|
||||
The GUI is built with **Tauri v2** using a lightweight React frontend:
|
||||
- **Tauri Commands**: Defined in `gui/src-tauri/src/commands.rs` exposing async Rust functions (`upload_file`, `get_accounts`, `login_v2_start`, `login_v2_poll`, `set_default_account`, etc.) to TypeScript.
|
||||
- **Event Streaming**: As WebDAV uploads progress, the Rust backend emits `upload-progress` events to the Tauri webview containing `file_id`, `bytes_transferred`, `total_bytes`, and `percent`.
|
||||
- **Drag-and-Drop Integration**: The native OS drag-and-drop listener intercepts dropped files and invokes `get_file_info` to populate the upload queue.
|
||||
|
||||
---
|
||||
|
||||
## 4. Developer Setup & Testing
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Run all unit and integration tests across the workspace
|
||||
cargo test --workspace
|
||||
|
||||
# Run CLI tests only
|
||||
cargo test -p nut
|
||||
|
||||
# Run core library tests only
|
||||
cargo test -p nextcloud_client
|
||||
```
|
||||
|
||||
### Running GUI in Development Mode
|
||||
```bash
|
||||
# In terminal 1: start Vite dev server
|
||||
cd gui
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# In terminal 2: run Tauri dev desktop app
|
||||
cargo run -p gui
|
||||
# Or use Tauri CLI
|
||||
npm run tauri dev
|
||||
```
|
||||
131
docs/automation-recipes.md
Normal file
131
docs/automation-recipes.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# CLI Automation & Scripting Recipes
|
||||
|
||||
This guide contains practical recipes for integrating `nut` into bash scripts, Unix pipelines, CI/CD runners, and headless server environments.
|
||||
|
||||
---
|
||||
|
||||
## 1. Piping Streams & Backups
|
||||
|
||||
### Piping Database Backups Directly to Nextcloud
|
||||
Stream database dumps straight to Nextcloud without creating intermediate local files:
|
||||
|
||||
```bash
|
||||
# MySQL / MariaDB Dump
|
||||
mysqldump -u root -p mydb | gzip -9 | \
|
||||
nut upload -d "Backups/MySQL" --stdin --filename "mydb-$(date +%Y%m%d).sql.gz"
|
||||
|
||||
# PostgreSQL Dump
|
||||
pg_dump mydb | zstd | \
|
||||
nut upload -d "Backups/Postgres" --stdin --filename "mydb-$(date +%Y%m%d).sql.zst"
|
||||
```
|
||||
|
||||
### Piping Compressed Archives with `pv` Rate Metering
|
||||
```bash
|
||||
# Compress and stream with live throughput monitoring
|
||||
tar -czf - /var/log/nginx | pv | \
|
||||
nut upload -d "Backups/Logs" --stdin --filename "nginx-logs-$(date +%F).tar.gz"
|
||||
```
|
||||
|
||||
### Piping from `curl` / `wget`
|
||||
Download a remote file and forward it to Nextcloud in one pipeline:
|
||||
```bash
|
||||
curl -fsSL https://example.com/large-dataset.csv.gz | \
|
||||
nut upload -s -d "Datasets" --stdin --filename "large-dataset.csv.gz"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Scripting with `jq`, `xargs`, and Clipboard
|
||||
|
||||
### Uploading and Instant Clipboard Copy (macOS, Linux, Windows)
|
||||
```bash
|
||||
# macOS
|
||||
nut upload -s --url-only screenshot.png | pbcopy
|
||||
|
||||
# Linux (X11 / Wayland)
|
||||
nut upload -s --url-only screenshot.png | xclip -selection clipboard
|
||||
# or wl-copy
|
||||
nut upload -s --url-only screenshot.png | wl-copy
|
||||
|
||||
# Windows PowerShell
|
||||
nut upload -s --url-only screenshot.png | Set-Clipboard
|
||||
```
|
||||
|
||||
### Batch Processing with `xargs`
|
||||
Upload all modified markdown files found with `find`:
|
||||
```bash
|
||||
find ./notes -name "*.md" -mtime -1 -print0 | \
|
||||
xargs -0 nut upload -s -d "Notes/Daily" --json
|
||||
```
|
||||
|
||||
### Extracting Links and Metadata with `jq`
|
||||
```bash
|
||||
# Extract all public share URLs into a text list
|
||||
nut upload -s -r assets/ --json | jq -r '.[].share_url // empty' > links.txt
|
||||
|
||||
# Query direct download links
|
||||
nut upload -s build/*.pkg --json | jq -r '.[] | select(.success == true) | "\(.file): \(.direct_download_url)"'
|
||||
```
|
||||
|
||||
### Scripting with TSV
|
||||
Parse tabular output in bash loops:
|
||||
```bash
|
||||
nut upload -s -r photos/ --tsv | while IFS=$'\t' read -r file remote bytes success share direct; do
|
||||
if [ "$success" = "true" ]; then
|
||||
echo "Uploaded $file ($bytes bytes) -> $share"
|
||||
else
|
||||
echo "Failed to upload $file"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. CI/CD & Headless Automation
|
||||
|
||||
### Non-Interactive CI / GitHub Actions Setup
|
||||
Authenticate non-interactively using environment secrets in CI pipelines:
|
||||
|
||||
```yaml
|
||||
name: Deploy Build Artifacts
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install nut
|
||||
run: |
|
||||
curl -fsSL https://github.com/majinnaibu/nextcloud-upload-tool/releases/latest/download/nut-x86_64-unknown-linux-musl.tar.gz | tar -xz
|
||||
sudo mv nut /usr/local/bin/
|
||||
|
||||
- name: Authenticate Nextcloud
|
||||
env:
|
||||
NC_SERVER: ${{ secrets.NEXTCLOUD_SERVER_URL }}
|
||||
NC_USER: ${{ secrets.NEXTCLOUD_USERNAME }}
|
||||
NC_PASS: ${{ secrets.NEXTCLOUD_APP_PASSWORD }}
|
||||
run: |
|
||||
nut login "$NC_SERVER" -u "$NC_USER" -p "$NC_PASS" --label "CI"
|
||||
|
||||
- name: Build and Upload Artifact
|
||||
run: |
|
||||
tar -czf release-build.tar.gz dist/
|
||||
SHARE_URL=$(nut upload -s --account "CI" --url-only release-build.tar.gz)
|
||||
echo "Download link: $SHARE_URL"
|
||||
```
|
||||
|
||||
### Headless SSH Remote Server Authorization
|
||||
When working over an SSH connection without X11 or desktop forwarding:
|
||||
|
||||
```bash
|
||||
# Run login in headless mode
|
||||
nut login https://cloud.example.com --no-browser --label "ProductionServer"
|
||||
|
||||
# nut prints:
|
||||
# ==> Please authorize access in your browser:
|
||||
# https://cloud.example.com/index.php/login/v2/flow/abc123xyz
|
||||
# ==> Waiting for browser authorization...
|
||||
|
||||
# Open that link on your local computer, authorize, and nut immediately captures the token!
|
||||
```
|
||||
165
docs/cli-reference.md
Normal file
165
docs/cli-reference.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Nextcloud Upload Tool (`nut`) — CLI Reference
|
||||
|
||||
The `nut` CLI provides a fast, pipe-friendly command-line interface for interacting with Nextcloud instances.
|
||||
|
||||
---
|
||||
|
||||
## Command Syntax
|
||||
|
||||
```bash
|
||||
nut <COMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
### Global Flags
|
||||
- `-h`, `--help`: Print help documentation.
|
||||
- `-V`, `--version`: Print version information.
|
||||
|
||||
---
|
||||
|
||||
## 1. `nut login`
|
||||
|
||||
Authenticates and saves a Nextcloud instance account into your operating system's native keychain.
|
||||
|
||||
```bash
|
||||
nut login <SERVER_URL> [OPTIONS]
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- `<SERVER_URL>`: The base URL of the Nextcloud instance (e.g. `https://cloud.example.com` or `http://localhost:8080/nextcloud`).
|
||||
|
||||
### Options
|
||||
- `--label <LABEL>`: Optional friendly name for this account (e.g. `Work`, `Personal`, `Staging`).
|
||||
- `--default <BOOL>`: Mark this account as the default active account (default: `true`).
|
||||
- `--no-browser`: Do not attempt to launch a local browser. Prints the Login Flow v2 URL to stdout for copy-pasting (ideal for SSH sessions).
|
||||
- `-m`, `--manual`: Interactively prompts for username and app password/token via terminal inputs.
|
||||
- `-u`, `--username <USER>`: Username for non-interactive / automated login (requires `-p` / `--app-password`).
|
||||
- `-p`, `--app-password <PASS>`: App password or token for non-interactive login (requires `-u` / `--username`).
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
# Interactive browser authorization (default)
|
||||
nut login https://cloud.example.com --label "Personal"
|
||||
|
||||
# Headless SSH authorization
|
||||
nut login https://cloud.example.com --no-browser --label "Server-Backup"
|
||||
|
||||
# Interactive terminal credential entry
|
||||
nut login https://cloud.example.com --manual
|
||||
|
||||
# Automated non-interactive CI / container provisioning
|
||||
nut login https://cloud.example.com -u ci-bot -p "xxxx-xxxx-xxxx-xxxx" --label "CI"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `nut upload`
|
||||
|
||||
Uploads one or more files, folders, or standard input streams to Nextcloud with optional public share generation.
|
||||
|
||||
```bash
|
||||
nut upload [FILE]... [OPTIONS]
|
||||
```
|
||||
|
||||
### Arguments
|
||||
- `[FILE]...`: One or more local file or directory paths to upload.
|
||||
|
||||
### Options
|
||||
- `-a`, `--account <ACCOUNT>`: Override default account with a specific account ID or label.
|
||||
- `-d`, `--remote-dir <DIR>`: Destination directory on Nextcloud (default: `Uploads`).
|
||||
- `-s`, `--share`: Automatically create a public share link after upload.
|
||||
- `--password <PASSWORD>`: Set a password for the generated public share link.
|
||||
- `-r`, `--recursive`: Recursively upload directories and nested directory hierarchies.
|
||||
- `-c`, `--continue-on-error`: Continue processing remaining files if one fails.
|
||||
- `--no-progress`: Disable progress bars even when running in an interactive TTY.
|
||||
- `--stdin`: Upload standard input data stream.
|
||||
- `--filename <NAME>`: Remote filename to use when uploading via `--stdin` (default: `stdin_upload.txt`).
|
||||
- `--size <BYTES>`: Expected size in bytes for stdin streams (enables ETA and progress bar).
|
||||
|
||||
### Formatting Options
|
||||
- `--json`: Output machine-readable JSON (array for batches, object for single file).
|
||||
- `--tsv`: Output Tab-Separated Values (file, remote path, bytes, success, share URL, direct URL).
|
||||
- `--url-only`: Output only the public share URL (requires `--share`).
|
||||
- `--direct-url-only`: Output only the direct download URL (requires `--share`).
|
||||
- `-q`, `--quiet`: Suppress progress output and print only share links or uploaded paths.
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
# Upload a single file with public share link
|
||||
nut upload -s report.pdf
|
||||
|
||||
# Upload multiple files into a remote folder
|
||||
nut upload -s -d "Documents/2026" sheet1.xlsx sheet2.xlsx
|
||||
|
||||
# Recursive directory upload
|
||||
nut upload -s -r assets/
|
||||
|
||||
# Upload standard input stream with custom filename
|
||||
cat database.sql | nut upload -s --stdin --filename "database.sql"
|
||||
|
||||
# Upload and copy public share URL directly to clipboard
|
||||
nut upload -s --url-only image.png | pbcopy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `nut accounts` / `nut account`
|
||||
|
||||
Manage stored Nextcloud accounts and configure default active accounts.
|
||||
|
||||
### `nut accounts` / `nut account list`
|
||||
List all stored accounts and their status.
|
||||
```bash
|
||||
nut accounts
|
||||
nut accounts --json
|
||||
nut accounts --tsv
|
||||
```
|
||||
|
||||
### `nut account default <ACCOUNT>`
|
||||
Set the active default account by ID or friendly label.
|
||||
```bash
|
||||
nut account default Work
|
||||
nut account default alice@cloud.example.com
|
||||
```
|
||||
|
||||
### `nut account delete <ACCOUNT>`
|
||||
Remove an account and wipe its token from the OS keychain.
|
||||
```bash
|
||||
nut account delete "Personal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `nut completions`
|
||||
|
||||
Generates shell completion scripts for your preferred shell environment.
|
||||
|
||||
```bash
|
||||
nut completions <SHELL>
|
||||
```
|
||||
|
||||
### Supported Shells
|
||||
- `bash`
|
||||
- `zsh`
|
||||
- `fish`
|
||||
- `powershell`
|
||||
- `elvish`
|
||||
|
||||
### Installation Examples
|
||||
```bash
|
||||
# Zsh
|
||||
nut completions zsh > ~/.zfunc/_nut
|
||||
|
||||
# Bash
|
||||
nut completions bash > /etc/bash_completion.d/nut
|
||||
# Or load in ~/.bashrc:
|
||||
source <(nut completions bash)
|
||||
|
||||
# Fish
|
||||
nut completions fish > ~/.config/fish/completions/nut.fish
|
||||
|
||||
# PowerShell
|
||||
nut completions powershell >> $PROFILE
|
||||
|
||||
# Elvish
|
||||
nut completions elvish > ~/.elvish/lib/nut.elv
|
||||
```
|
||||
51
docs/gui-guide.md
Normal file
51
docs/gui-guide.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# GUI Desktop Application Guide
|
||||
|
||||
The **Nextcloud Upload Tool GUI** is a fast desktop application designed for streamlined file queuing, drag-and-drop uploads, public share creation, and multi-account management.
|
||||
|
||||
---
|
||||
|
||||
## 1. Interface Overview
|
||||
|
||||
The interface is structured into three primary sections:
|
||||
|
||||
1. **Header Bar**:
|
||||
- Displays the application branding.
|
||||
- **Account Switcher**: Quick-switch dropdown to select the target Nextcloud account for uploads.
|
||||
- **Navigation Tabs**: Toggle between **Upload Queue** and **Accounts & Auth**.
|
||||
|
||||
2. **Upload Queue Tab**:
|
||||
- **Target Settings**: Configure remote directory on Nextcloud (default: `Uploads`), toggle public share generation, and set optional link passwords.
|
||||
- **Drag & Drop Zone**: Visual target supporting OS file drops or manual system file browser selection (`Browse Files`).
|
||||
- **Queue List**: Per-file upload queue showing status badges (`queued`, `uploading`, `completed`, `error`), live percentage, byte counters, and action buttons (`▲`, `▼`, `✕`, inline `↻ Retry`).
|
||||
- **Global Actions**: `Upload All (N)` button and `Clear Completed` / `Clear All`.
|
||||
|
||||
3. **Accounts & Auth Tab**:
|
||||
- **Connected Accounts**: Interactive cards displaying connected usernames, server endpoints, custom labels, and active default status.
|
||||
- **Browser Login (Login Flow v2)**: One-click interactive authorization opening your web browser with support for 2FA / SSO.
|
||||
- **Manual App Password Entry**: Form for connecting with custom server URLs, usernames, and generated application tokens.
|
||||
|
||||
---
|
||||
|
||||
## 2. Step-by-Step Walkthrough
|
||||
|
||||
### Connecting Your First Account
|
||||
1. Open the application and switch to the **Accounts & Auth** tab.
|
||||
2. Enter your Nextcloud server URL (e.g. `https://cloud.example.com`).
|
||||
3. Click **Authorize in Browser (Login Flow v2)**.
|
||||
4. Your default browser opens the authorization page. Click **Grant Access**.
|
||||
5. The GUI automatically polls and securely stores your token in your operating system's native keychain.
|
||||
|
||||
### Uploading Files & Creating Share Links
|
||||
1. Navigate to the **Upload Queue** tab.
|
||||
2. Drag and drop one or more files from Finder / Explorer / File Manager into the dropzone.
|
||||
3. (Optional) Check **Generate public share link** and specify a **Share password** if needed.
|
||||
4. Set the **Remote Directory** (e.g. `Projects/Assets` or `Uploads`).
|
||||
5. Click **Upload All**.
|
||||
6. Real-time progress bars update for both the total batch and each individual file.
|
||||
7. Once finished:
|
||||
- Click **Copy Link** to copy the public share URL to clipboard.
|
||||
- Click **Copy Direct Download** to copy the raw download URL.
|
||||
|
||||
### Managing Failed Uploads
|
||||
- If any file fails (e.g. due to network glitch or server quota), an error badge is displayed with details.
|
||||
- Click the inline **↻ Retry** button next to that item to re-attempt the upload immediately without resetting the queue.
|
||||
77
docs/installation.md
Normal file
77
docs/installation.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Installation Guide
|
||||
|
||||
**Nextcloud Upload Tool (`nut`)** is available as a standalone command-line tool and a desktop GUI application across macOS, Windows, and Linux.
|
||||
|
||||
---
|
||||
|
||||
## 1. Pre-built Binaries & Installers
|
||||
|
||||
Download pre-built releases for your operating system from the [Releases](https://github.com/majinnaibu/nextcloud-upload-tool/releases) page.
|
||||
|
||||
### macOS (Apple Silicon & Intel)
|
||||
- **GUI Desktop App**: Download the `.dmg` installer, open it, and drag `Nextcloud Upload Tool.app` into `/Applications`.
|
||||
- **CLI Binary**:
|
||||
```bash
|
||||
# Apple Silicon (M1/M2/M3/M4)
|
||||
curl -fsSL https://github.com/majinnaibu/nextcloud-upload-tool/releases/latest/download/nut-aarch64-apple-darwin.tar.gz | tar -xz
|
||||
sudo mv nut /usr/local/bin/
|
||||
|
||||
# Intel x86_64
|
||||
curl -fsSL https://github.com/majinnaibu/nextcloud-upload-tool/releases/latest/download/nut-x86_64-apple-darwin.tar.gz | tar -xz
|
||||
sudo mv nut /usr/local/bin/
|
||||
```
|
||||
|
||||
### Linux (Debian, Ubuntu, Fedora, Arch, Generic)
|
||||
- **Debian / Ubuntu (`.deb`)**:
|
||||
```bash
|
||||
sudo dpkg -i nextcloud-upload-tool_0.1.0_amd64.deb
|
||||
sudo apt-get install -f
|
||||
```
|
||||
- **Fedora / RHEL (`.rpm`)**:
|
||||
```bash
|
||||
sudo rpm -i nextcloud-upload-tool-0.1.0-1.x86_64.rpm
|
||||
```
|
||||
- **Universal AppImage**:
|
||||
```bash
|
||||
chmod +x Nextcloud_Upload_Tool_0.1.0_amd64.AppImage
|
||||
./Nextcloud_Upload_Tool_0.1.0_amd64.AppImage
|
||||
```
|
||||
- **Static CLI Binary (musl libc - zero external dependencies)**:
|
||||
```bash
|
||||
curl -fsSL https://github.com/majinnaibu/nextcloud-upload-tool/releases/latest/download/nut-x86_64-unknown-linux-musl.tar.gz | tar -xz
|
||||
sudo mv nut /usr/local/bin/
|
||||
```
|
||||
|
||||
### Windows
|
||||
- **GUI Installer (`.msi` / NSIS `.exe`)**: Download and run `Nextcloud-Upload-Tool-Setup.exe` or `Nextcloud-Upload-Tool.msi`. Follow the installer wizard.
|
||||
- **Standalone CLI**: Download `nut-x86_64-pc-windows-msvc.zip`, extract `nut.exe`, and add it to your system `%PATH%`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Building from Source
|
||||
|
||||
### Prerequisites
|
||||
- **Rust toolchain** (1.75+): Install via `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
|
||||
- **Node.js** (v20+) and **npm** (for GUI frontend build)
|
||||
- **Platform Development Libraries**:
|
||||
- **Linux**: `sudo apt-get install libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libssl-dev patchelf`
|
||||
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
|
||||
- **Windows**: Visual Studio C++ Build Tools
|
||||
|
||||
### Build the CLI (`nut`)
|
||||
```bash
|
||||
git clone https://github.com/majinnaibu/nextcloud-upload-tool.git
|
||||
cd nextcloud-upload-tool
|
||||
|
||||
cargo build --release -p nut
|
||||
# The binary is placed at target/release/nut
|
||||
```
|
||||
|
||||
### Build the Desktop GUI
|
||||
```bash
|
||||
cd gui
|
||||
npm install
|
||||
npm run build
|
||||
npm run tauri build
|
||||
# The packaged bundle is located in gui/src-tauri/target/release/bundle/
|
||||
```
|
||||
92
docs/multi-account.md
Normal file
92
docs/multi-account.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Multi-Account Management Guide
|
||||
|
||||
**Nextcloud Upload Tool** supports managing multiple Nextcloud instances and user accounts simultaneously. Credentials, account metadata, and active default states are shared seamlessly between the CLI and GUI frontends.
|
||||
|
||||
---
|
||||
|
||||
## 1. How Accounts Are Identified
|
||||
|
||||
Every configured account has a canonical ID in the format:
|
||||
```
|
||||
username@host
|
||||
```
|
||||
*(e.g. `alice@cloud.example.com` or `dev@nextcloud.local`)*
|
||||
|
||||
You can also assign an optional friendly **label** (e.g. `"Work"`, `"Personal"`, `"Backup"`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Managing Accounts via CLI (`nut`)
|
||||
|
||||
### Listing Configured Accounts
|
||||
```bash
|
||||
nut accounts
|
||||
# or
|
||||
nut account list
|
||||
```
|
||||
Outputs:
|
||||
```text
|
||||
Configured Nextcloud Accounts:
|
||||
|
||||
• alice@cloud.example.com * (active default)
|
||||
Username: alice
|
||||
Server: https://cloud.example.com
|
||||
Label: Work
|
||||
|
||||
• bob@nextcloud.org
|
||||
Username: bob
|
||||
Server: https://nextcloud.org
|
||||
Label: Personal
|
||||
```
|
||||
|
||||
To output as JSON or TSV for scripting:
|
||||
```bash
|
||||
nut accounts --json
|
||||
nut accounts --tsv
|
||||
```
|
||||
|
||||
### Adding an Account
|
||||
- **Interactive Browser Flow**:
|
||||
```bash
|
||||
nut login https://cloud.example.com --label "Work"
|
||||
```
|
||||
- **Interactive Terminal Password Prompt**:
|
||||
```bash
|
||||
nut login https://cloud.example.com --manual --label "Work"
|
||||
```
|
||||
- **Non-Interactive / Headless / Scripting**:
|
||||
```bash
|
||||
nut login https://cloud.example.com -u alice -p "app-password-token" --label "Work"
|
||||
```
|
||||
|
||||
### Switching the Active Default Account
|
||||
```bash
|
||||
# Set default by label
|
||||
nut account default Work
|
||||
|
||||
# Or set default by full account ID
|
||||
nut account default alice@cloud.example.com
|
||||
```
|
||||
|
||||
### Uploading to a Specific Account
|
||||
Use the `--account` / `-a` flag on any upload command to override the active default for that invocation:
|
||||
```bash
|
||||
nut upload --account "Work" report.pdf
|
||||
nut upload --account "bob@nextcloud.org" archive.zip
|
||||
```
|
||||
|
||||
### Removing an Account
|
||||
```bash
|
||||
nut account delete "Work"
|
||||
```
|
||||
This removes the account from `~/.config/nut/accounts.json` and deletes the associated app token from your OS keychain.
|
||||
|
||||
---
|
||||
|
||||
## 3. Managing Accounts in the GUI
|
||||
|
||||
1. Open the **Accounts & Auth** tab.
|
||||
2. The list of connected accounts displays each account's server URL, username, label, and whether it is marked as default.
|
||||
3. Click **Set as Default** on any account card to switch the primary default.
|
||||
4. Click **Select as Active** to switch the current session's upload destination.
|
||||
5. Click **Logout / Remove** to purge credentials from the OS keychain.
|
||||
@@ -1,11 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tauri::Emitter;
|
||||
|
||||
use nextcloud_client::{
|
||||
initiate_login_flow as api_initiate_login_flow,
|
||||
poll_login_flow as api_poll_login_flow,
|
||||
ClientConfig, CredentialStore, NextcloudClient, StoredAccount, UploadOptions,
|
||||
ClientConfig, CredentialStore, NextcloudClient, ProgressEvent, StoredAccount, UploadOptions,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -32,6 +34,13 @@ pub struct GuiUploadResult {
|
||||
pub direct_download_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct GuiUploadProgressPayload {
|
||||
pub file_path: String,
|
||||
pub bytes_transferred: u64,
|
||||
pub total_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_accounts() -> Result<Vec<StoredAccount>, String> {
|
||||
CredentialStore::list_accounts().map_err(|e| e.to_string())
|
||||
@@ -184,6 +193,7 @@ pub fn get_file_info(file_path: String) -> Result<FileInfo, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn upload_file(
|
||||
app: tauri::AppHandle,
|
||||
file_path: String,
|
||||
remote_dir: String,
|
||||
create_share: bool,
|
||||
@@ -222,8 +232,23 @@ pub async fn upload_file(
|
||||
overwrite: true,
|
||||
};
|
||||
|
||||
let app_handle = app.clone();
|
||||
let fp = file_path.clone();
|
||||
let progress_cb: nextcloud_client::ProgressCallback = Arc::new(move |event| {
|
||||
if let ProgressEvent::Progress { bytes_transferred, total_bytes } = event {
|
||||
let _ = app_handle.emit(
|
||||
"upload-progress",
|
||||
GuiUploadProgressPayload {
|
||||
file_path: fp.clone(),
|
||||
bytes_transferred,
|
||||
total_bytes,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let res = client
|
||||
.upload_and_share(&local_path, &options, None)
|
||||
.upload_and_share(&local_path, &options, Some(progress_cb))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -239,7 +264,7 @@ pub async fn upload_file(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FileInfo, GuiUploadResult, LoginFlowInitPayload};
|
||||
use super::{FileInfo, GuiUploadProgressPayload, GuiUploadResult, LoginFlowInitPayload};
|
||||
|
||||
#[test]
|
||||
fn test_login_flow_payload_serialization() {
|
||||
@@ -267,6 +292,19 @@ mod tests {
|
||||
assert_eq!(file_info, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upload_progress_payload_serialization() {
|
||||
let payload = GuiUploadProgressPayload {
|
||||
file_path: "/tmp/sample.iso".to_string(),
|
||||
bytes_transferred: 5242880,
|
||||
total_bytes: Some(10485760),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&payload).unwrap();
|
||||
let deserialized: GuiUploadProgressPayload = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(payload, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gui_upload_result_serialization() {
|
||||
let res = GuiUploadResult {
|
||||
|
||||
@@ -31,6 +31,38 @@
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
],
|
||||
"category": "Utility",
|
||||
"shortDescription": "Fast, seamless Nextcloud file uploader and share link generator",
|
||||
"longDescription": "Upload files to Nextcloud with ease, automatically generate share links, and manage multiple accounts with secure OS keychain storage.",
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": [
|
||||
"libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37",
|
||||
"libayatana-appindicator3-1 | libappindicator3-1",
|
||||
"librsvg2-common"
|
||||
]
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"webkit2gtk4.1",
|
||||
"libappindicator-gtk3"
|
||||
]
|
||||
}
|
||||
},
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "10.15",
|
||||
"dmg": {
|
||||
"windowSize": {
|
||||
"width": 600,
|
||||
"height": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"installMode": "currentUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
324
gui/src/App.css
324
gui/src/App.css
@@ -102,6 +102,47 @@ body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Navigation Tabs */
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
background: var(--surface-color);
|
||||
color: var(--text-muted);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--text-main);
|
||||
background: var(--surface-card);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Content Area */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
@@ -169,6 +210,71 @@ body {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Overall Progress */
|
||||
.overall-progress-container {
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.overall-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overall-progress-title {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.overall-progress-stats {
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.progress-bar-track {
|
||||
background: var(--surface-card);
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
background: linear-gradient(90deg, var(--primary), var(--accent));
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.progress-bar-animated {
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
rgba(255, 255, 255, 0.15) 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.15) 75%,
|
||||
transparent 75%,
|
||||
transparent
|
||||
);
|
||||
background-size: 24px 24px;
|
||||
animation: move-stripes 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes move-stripes {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Queue List */
|
||||
.queue-title-meta {
|
||||
display: flex;
|
||||
@@ -204,7 +310,7 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: 280px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
@@ -216,7 +322,7 @@ body {
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 12px;
|
||||
padding: 10px 12px;
|
||||
transition: background-color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
@@ -265,10 +371,74 @@ body {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Item Progress Bars */
|
||||
.item-progress-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.item-progress-track {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: var(--surface-card);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-progress-fill {
|
||||
background: var(--accent);
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
transition: width 0.15s ease;
|
||||
}
|
||||
|
||||
.item-progress-animated {
|
||||
background: linear-gradient(90deg, var(--primary), var(--accent));
|
||||
animation: pulse-glow 1.5s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item-progress-text {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.queue-item-error {
|
||||
font-size: 11px;
|
||||
color: var(--danger);
|
||||
margin-top: 2px;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-retry-inline {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #fca5a5;
|
||||
border: 1px solid var(--danger);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-retry-inline:hover {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.queue-item-size {
|
||||
@@ -354,6 +524,154 @@ body {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
/* Account Cards */
|
||||
.account-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 14px;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.account-card.active {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 1px var(--primary);
|
||||
}
|
||||
|
||||
.account-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.account-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.badge-default {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
color: var(--success);
|
||||
border: 1px solid var(--success);
|
||||
}
|
||||
|
||||
.badge-active {
|
||||
background: rgba(2, 132, 199, 0.2);
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.account-card-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.account-card-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Auth Mode Toggle */
|
||||
.auth-mode-toggle {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn-mode {
|
||||
flex: 1;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 12px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-mode:hover:not(:disabled) {
|
||||
color: var(--text-main);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-mode.active {
|
||||
background: var(--surface-card);
|
||||
border-color: var(--primary);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.auth-form-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Polling State */
|
||||
.polling-box {
|
||||
background: var(--bg-color);
|
||||
border: 1px dashed var(--accent);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.polling-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.polling-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--accent);
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Controls */
|
||||
.form-group {
|
||||
margin-bottom: 12px;
|
||||
|
||||
1080
gui/src/App.tsx
1080
gui/src/App.tsx
File diff suppressed because it is too large
Load Diff
@@ -345,4 +345,42 @@ mod tests {
|
||||
let deserialized: StoredAccount = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized, account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unified_config_accounts_parsing() {
|
||||
let json_data = r#"[
|
||||
{
|
||||
"id": "alice@cloud.example.com",
|
||||
"label": "Work",
|
||||
"username": "alice",
|
||||
"server_url": "https://cloud.example.com",
|
||||
"is_default": true,
|
||||
"fallback_password": "app-password-token"
|
||||
},
|
||||
{
|
||||
"id": "bob@personal.nextcloud.com",
|
||||
"label": "Personal",
|
||||
"username": "bob",
|
||||
"server_url": "https://personal.nextcloud.com",
|
||||
"is_default": false
|
||||
}
|
||||
]"#;
|
||||
|
||||
let accounts: Vec<StoredAccount> = serde_json::from_str(json_data).unwrap();
|
||||
assert_eq!(accounts.len(), 2);
|
||||
assert_eq!(accounts[0].id, "alice@cloud.example.com");
|
||||
assert_eq!(accounts[0].label.as_deref(), Some("Work"));
|
||||
assert!(accounts[0].is_default);
|
||||
assert_eq!(accounts[0].fallback_password.as_deref(), Some("app-password-token"));
|
||||
|
||||
assert_eq!(accounts[1].id, "bob@personal.nextcloud.com");
|
||||
assert_eq!(accounts[1].label.as_deref(), Some("Personal"));
|
||||
assert!(!accounts[1].is_default);
|
||||
assert_eq!(accounts[1].fallback_password, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyring_constants() {
|
||||
assert_eq!(KEYRING_SERVICE_NAME, "me.majinnaibu.nut");
|
||||
}
|
||||
}
|
||||
|
||||
32
scripts/build-cli.sh
Executable file
32
scripts/build-cli.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Nextcloud Upload Tool — CLI Packaging Helper Script
|
||||
# Builds release binaries and packages archives for distribution
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
DIST_DIR="${ROOT_DIR}/dist-packages"
|
||||
|
||||
echo "==> Building static/optimized release CLI binary..."
|
||||
cd "${ROOT_DIR}"
|
||||
cargo build --release -p nut
|
||||
|
||||
TARGET_BIN="${ROOT_DIR}/target/release/nut"
|
||||
VERSION=$(cargo pkgid -p nut | cut -d# -f2 | cut -d: -f2 || echo "0.1.0")
|
||||
ARCH=$(uname -m)
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
mkdir -p "${DIST_DIR}"
|
||||
ARCHIVE_NAME="nut-v${VERSION}-${OS}-${ARCH}.tar.gz"
|
||||
|
||||
echo "==> Creating distribution archive: ${ARCHIVE_NAME}"
|
||||
TMP_STAGE=$(mktemp -d)
|
||||
cp "${TARGET_BIN}" "${TMP_STAGE}/nut"
|
||||
cp "${ROOT_DIR}/README.md" "${TMP_STAGE}/"
|
||||
cp "${ROOT_DIR}/LICENSE" "${TMP_STAGE}/"
|
||||
|
||||
tar -czf "${DIST_DIR}/${ARCHIVE_NAME}" -C "${TMP_STAGE}" .
|
||||
rm -rf "${TMP_STAGE}"
|
||||
|
||||
echo "✓ CLI package created successfully at: ${DIST_DIR}/${ARCHIVE_NAME}"
|
||||
18
scripts/build-gui.sh
Executable file
18
scripts/build-gui.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Nextcloud Upload Tool — Tauri GUI Packaging Helper Script
|
||||
# Builds production frontend and packages native desktop installer/bundles
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
GUI_DIR="${ROOT_DIR}/gui"
|
||||
|
||||
echo "==> Building frontend assets..."
|
||||
cd "${GUI_DIR}"
|
||||
npm run build
|
||||
|
||||
echo "==> Packaging native desktop bundle with Tauri..."
|
||||
npm run tauri build
|
||||
|
||||
echo "✓ Native desktop bundle packaged in: ${GUI_DIR}/src-tauri/target/release/bundle/"
|
||||
Reference in New Issue
Block a user