166 lines
5.0 KiB
Rust
166 lines
5.0 KiB
Rust
use crate::models::{
|
|
sanitize_filename, AppState, IncomingTextPayload, MediaItem, PeerNode, TextItem,
|
|
UiSendTextRequest, UiTrustRequest, MEDIA_DIR,
|
|
};
|
|
use axum::{
|
|
body::Bytes,
|
|
extract::{Path, State},
|
|
http::{header, HeaderMap, StatusCode},
|
|
response::{Html, IntoResponse},
|
|
Json,
|
|
};
|
|
use std::{path::PathBuf, sync::Arc};
|
|
|
|
/// Вшиваем внешний файл src/static/index.html в бинарник на этапе компиляции
|
|
const INDEX_HTML: &str = include_str!("static/index.html");
|
|
|
|
pub async fn ui_index() -> Html<&'static str> {
|
|
Html(INDEX_HTML)
|
|
}
|
|
|
|
pub async fn ui_get_state(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
|
|
let trusted_map = state.trusted_fingerprints.read().unwrap();
|
|
let mut peers_list: Vec<PeerNode> = state
|
|
.peers
|
|
.read()
|
|
.unwrap()
|
|
.values()
|
|
.cloned()
|
|
.map(|mut p| {
|
|
p.verified = trusted_map
|
|
.get(&p.ip)
|
|
.map(|fp| fp.eq_ignore_ascii_case(&p.fingerprint))
|
|
.unwrap_or(false);
|
|
p
|
|
})
|
|
.collect();
|
|
peers_list.sort_by(|a, b| a.name.cmp(&b.name));
|
|
|
|
let texts: Vec<TextItem> = state.ram_texts.read().unwrap().iter().cloned().collect();
|
|
let media: Vec<MediaItem> = state.disk_media.read().unwrap().iter().cloned().collect();
|
|
|
|
Json(serde_json::json!({
|
|
"device_name": state.device_name,
|
|
"my_ip": state.my_ip,
|
|
"p2p_port": state.p2p_port,
|
|
"my_fingerprint": state.my_fingerprint,
|
|
"my_pin": state.my_fingerprint[..8].to_uppercase(),
|
|
"peers": peers_list,
|
|
"texts": texts,
|
|
"media": media
|
|
}))
|
|
}
|
|
|
|
pub async fn ui_trust_peer(
|
|
State(state): State<Arc<AppState>>,
|
|
Json(req): Json<UiTrustRequest>,
|
|
) -> Result<StatusCode, (StatusCode, String)> {
|
|
let peers = state.peers.read().unwrap();
|
|
let peer = peers
|
|
.get(&req.target_ip)
|
|
.ok_or((StatusCode::NOT_FOUND, "Узел не найден".to_string()))?;
|
|
|
|
let mut trusted = state.trusted_fingerprints.write().unwrap();
|
|
if req.trust {
|
|
trusted.insert(peer.ip.clone(), peer.fingerprint.clone());
|
|
} else {
|
|
trusted.remove(&peer.ip);
|
|
}
|
|
Ok(StatusCode::OK)
|
|
}
|
|
|
|
pub async fn ui_clear_ram(State(state): State<Arc<AppState>>) -> StatusCode {
|
|
state.ram_texts.write().unwrap().clear();
|
|
StatusCode::OK
|
|
}
|
|
|
|
pub async fn ui_send_text(
|
|
State(state): State<Arc<AppState>>,
|
|
Json(req): Json<UiSendTextRequest>,
|
|
) -> Result<StatusCode, (StatusCode, String)> {
|
|
let port = state
|
|
.peers
|
|
.read()
|
|
.unwrap()
|
|
.get(&req.target_ip)
|
|
.map(|p| p.port)
|
|
.unwrap_or(state.p2p_port);
|
|
|
|
let url = format!("https://{}:{}/p2p/text", req.target_ip, port);
|
|
let payload = IncomingTextPayload {
|
|
sender: state.device_name.clone(),
|
|
text: req.text,
|
|
};
|
|
|
|
let resp = state
|
|
.http_client
|
|
.post(&url)
|
|
.json(&payload)
|
|
.send()
|
|
.await
|
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Ошибка TLS/Сети: {}", e)))?;
|
|
|
|
if resp.status().is_success() {
|
|
Ok(StatusCode::OK)
|
|
} else {
|
|
Err((resp.status(), "Удаленный узел отклонил запрос".to_string()))
|
|
}
|
|
}
|
|
|
|
pub async fn ui_send_media(
|
|
State(state): State<Arc<AppState>>,
|
|
headers: HeaderMap,
|
|
body: Bytes,
|
|
) -> Result<StatusCode, (StatusCode, String)> {
|
|
let target_ip = headers
|
|
.get("X-Target-Ip")
|
|
.and_then(|v| v.to_str().ok())
|
|
.ok_or((StatusCode::BAD_REQUEST, "Не указан IP получателя".to_string()))?;
|
|
|
|
let filename = headers
|
|
.get("X-Filename")
|
|
.and_then(|v| v.to_str().ok())
|
|
.unwrap_or("file.bin");
|
|
|
|
let mime = headers
|
|
.get(header::CONTENT_TYPE)
|
|
.and_then(|v| v.to_str().ok())
|
|
.unwrap_or("application/octet-stream");
|
|
|
|
let port = state
|
|
.peers
|
|
.read()
|
|
.unwrap()
|
|
.get(target_ip)
|
|
.map(|p| p.port)
|
|
.unwrap_or(state.p2p_port);
|
|
|
|
let url = format!("https://{}:{}/p2p/media", target_ip, port);
|
|
|
|
let resp = state
|
|
.http_client
|
|
.post(&url)
|
|
.header("X-Sender", &state.device_name)
|
|
.header("X-Filename", filename)
|
|
.header(header::CONTENT_TYPE, mime)
|
|
.body(body)
|
|
.send()
|
|
.await
|
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Ошибка TLS/Сети: {}", e)))?;
|
|
|
|
if resp.status().is_success() {
|
|
Ok(StatusCode::OK)
|
|
} else {
|
|
Err((resp.status(), "Удаленный узел отклонил медиафайл".to_string()))
|
|
}
|
|
}
|
|
|
|
pub async fn ui_serve_saved_file(Path(filename): Path<String>) -> impl IntoResponse {
|
|
let safe_name = sanitize_filename(&filename);
|
|
let path = PathBuf::from(MEDIA_DIR).join(&safe_name);
|
|
match tokio::fs::read(&path).await {
|
|
Ok(bytes) => (StatusCode::OK, bytes).into_response(),
|
|
Err(_) => (StatusCode::NOT_FOUND, "Файл не найден на диске").into_response(),
|
|
}
|
|
}
|