Compare commits
3 Commits
77185a578d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dd2f00cd37 | |||
| fad5d0991b | |||
| 73c1f58a8f |
+16
@@ -0,0 +1,16 @@
|
||||
# Rust
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
Cargo.lock
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "rpc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.4"
|
||||
actix-cors = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
@@ -1,2 +1,133 @@
|
||||
# RPC
|
||||
# RPC - NAS Folder Opener
|
||||
|
||||
A local HTTP server that opens NAS folder paths in the system file explorer. Designed to work with the PRISM web application hosted at `prism.ccllc.pro`.
|
||||
|
||||
## Purpose
|
||||
|
||||
Browsers cannot directly open network file shares (NAS folders) due to security restrictions. This tool bridges that gap by:
|
||||
|
||||
1. Running a local HTTP server on `localhost:8080`
|
||||
2. Accepting requests from the PRISM web application
|
||||
3. Opening NAS folder paths in the user's file explorer
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **User visits PRISM** at `prism.ccllc.pro` and views job information
|
||||
2. **User clicks a folder link** from the `Job_Folder_Link` field
|
||||
3. **PRISM makes a request** to `http://localhost:8080/open?path=<NAS_PATH>`
|
||||
4. **RPC tool opens the path** in the system file explorer
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust toolchain installed ([rustup.rs](https://rustup.rs/))
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The binary will be in `target/release/rpc` (or `target/release/rpc.exe` on Windows).
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
Or run the release binary:
|
||||
|
||||
```bash
|
||||
./target/release/rpc
|
||||
```
|
||||
|
||||
The server will start on `http://127.0.0.1:8080`.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### `GET /open?path=<NAS_PATH>`
|
||||
|
||||
Opens a NAS folder path in the file explorer.
|
||||
|
||||
**Query Parameters:**
|
||||
- `path` (required): The NAS folder path to open. Supports:
|
||||
- UNC paths: `\\server\share\folder`
|
||||
- SMB paths: `smb://server/share/folder`
|
||||
- Unix-style: `//server/share/folder`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Opening path: \\\\server\\share\\folder"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Health check endpoint.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "NAS Folder Opener",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- The server only accepts requests from `https://prism.ccllc.pro` (CORS)
|
||||
- Paths are validated to ensure they are network shares
|
||||
- Server only binds to `127.0.0.1` (localhost), not exposed to network
|
||||
|
||||
## Integration with PRISM
|
||||
|
||||
In your Svelte component, add a function to open NAS folders:
|
||||
|
||||
```typescript
|
||||
async function openNasFolder(nasPath: string) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://localhost:8080/open?path=${encodeURIComponent(nasPath)}`
|
||||
);
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
alert(`Failed to open folder: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Please ensure the NAS opener tool is running on your computer');
|
||||
console.error('Error opening NAS folder:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then use it when displaying job folder links:
|
||||
|
||||
```svelte
|
||||
<button onclick={() => openNasFolder(job.Job_Folder_Link)}>
|
||||
Open Folder
|
||||
</button>
|
||||
```
|
||||
|
||||
## Cross-Platform Support
|
||||
|
||||
- **Windows**: Uses `explorer.exe`
|
||||
- **macOS**: Uses `open` command
|
||||
- **Linux**: Uses `xdg-open` command
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
use actix_cors::Cors;
|
||||
use actix_web::{web, App, HttpServer, HttpResponse, Result as ActixResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::process::Command;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OpenPathQuery {
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OpenPathResponse {
|
||||
success: bool,
|
||||
message: String,
|
||||
}
|
||||
|
||||
/// Opens a NAS path in the file explorer
|
||||
async fn open_path(query: web::Query<OpenPathQuery>) -> ActixResult<HttpResponse> {
|
||||
let path = query.path.trim();
|
||||
|
||||
// Validate that the path is a network path (UNC path on Windows or SMB path)
|
||||
// Accepts paths like: \\server\share, //server/share, or smb://server/share
|
||||
let is_valid_nas_path = path.starts_with("\\\\")
|
||||
|| path.starts_with("//")
|
||||
|| path.starts_with("smb://")
|
||||
|| path.starts_with("file://");
|
||||
|
||||
if !is_valid_nas_path {
|
||||
return Ok(HttpResponse::BadRequest().json(OpenPathResponse {
|
||||
success: false,
|
||||
message: "Invalid path. Path must be a network share (UNC path starting with \\\\ or //)".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Normalize the path for the OS
|
||||
let normalized_path = if path.starts_with("smb://") {
|
||||
// Convert smb:// to UNC format
|
||||
path.replace("smb://", "\\\\").replace("/", "\\")
|
||||
} else if path.starts_with("//") {
|
||||
// Convert // to \\
|
||||
path.replace("//", "\\\\")
|
||||
} else if path.starts_with("file://") {
|
||||
// Remove file:// prefix
|
||||
path.strip_prefix("file://").unwrap_or(path).to_string()
|
||||
} else {
|
||||
path.to_string()
|
||||
};
|
||||
|
||||
// Determine the OS and use the appropriate command
|
||||
let result = if cfg!(target_os = "windows") {
|
||||
// Windows: use explorer.exe
|
||||
Command::new("explorer")
|
||||
.arg(&normalized_path)
|
||||
.spawn()
|
||||
} else if cfg!(target_os = "macos") {
|
||||
// macOS: use open command
|
||||
Command::new("open")
|
||||
.arg(&normalized_path)
|
||||
.spawn()
|
||||
} else {
|
||||
// Linux: try xdg-open, fallback to other file managers
|
||||
Command::new("xdg-open")
|
||||
.arg(&normalized_path)
|
||||
.spawn()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(HttpResponse::Ok().json(OpenPathResponse {
|
||||
success: true,
|
||||
message: format!("Opening path: {}", normalized_path),
|
||||
})),
|
||||
Err(e) => {
|
||||
eprintln!("Error opening path: {}", e);
|
||||
Ok(HttpResponse::InternalServerError().json(OpenPathResponse {
|
||||
success: false,
|
||||
message: format!("Failed to open path: {}", e),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Health check endpoint
|
||||
async fn health() -> ActixResult<HttpResponse> {
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"status": "ok",
|
||||
"service": "NAS Folder Opener",
|
||||
"version": "0.1.0"
|
||||
})))
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
println!("Starting NAS Folder Opener server...");
|
||||
println!("Server will listen on http://127.0.0.1:8080");
|
||||
println!("Allowed origin: https://prism.ccllc.pro");
|
||||
|
||||
HttpServer::new(|| {
|
||||
// Configure CORS to allow requests from prism.ccllc.pro
|
||||
let cors = Cors::default()
|
||||
.allowed_origin("https://prism.ccllc.pro")
|
||||
.allowed_origin("http://localhost:5173") // Allow local dev
|
||||
.allowed_origin("http://127.0.0.1:5173") // Allow local dev
|
||||
.allowed_methods(vec!["GET", "POST", "OPTIONS"])
|
||||
.allowed_headers(vec![
|
||||
actix_web::http::header::CONTENT_TYPE,
|
||||
actix_web::http::header::AUTHORIZATION,
|
||||
])
|
||||
.max_age(3600);
|
||||
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
.route("/health", web::get().to(health))
|
||||
.route("/open", web::get().to(open_path))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
Reference in New Issue
Block a user