version a0.0.1

This commit is contained in:
netadmin committed 2026-10-22 20:45:13 +03:00
commit 9469ef10b6
19 files changed
+4281

No files matched your search

+219
View File
@@ -0,0 +1,219 @@
use crate::models::{
sanitize_filename, AppState, IncomingTextPayload, MediaItem, PeerNode, TextItem,
UiManualPeerRequest, 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};
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_add_manual_peer(
State(state): State<Arc<AppState>>,
Json(req): Json<UiManualPeerRequest>,
) -> Result<StatusCode, (StatusCode, String)> {
let ip = req.ip.trim().to_string();
let port = req.port.unwrap_or(8443);
let url = format!("https://{}:{}/p2p/info", ip, port);
let resp = state
.probe_client
.get(&url)
.send()
.await
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Узел недоступен: {}", e)))?;
if !resp.status().is_success() {
return Err((StatusCode::BAD_GATEWAY, "Узел вернул ошибку".into()));
}
let info: serde_json::Value = resp
.json()
.await
.map_err(|_| (StatusCode::BAD_GATEWAY, "Неверный ответ узла".into()))?;
let name = info["name"].as_str().unwrap_or("Manual-Node").to_string();
let fingerprint = info["fingerprint"].as_str().unwrap_or("").to_string();
if fingerprint.is_empty() {
return Err((StatusCode::BAD_GATEWAY, "Отсутствует отпечаток узла".into()));
}
let pin_code = fingerprint[..8].to_uppercase();
let is_verified = state
.trusted_fingerprints
.read()
.unwrap()
.get(&ip)
.map(|fp| fp.eq_ignore_ascii_case(&fingerprint))
.unwrap_or(false);
state.peers.write().unwrap().insert(
ip.clone(),
PeerNode {
name,
ip,
port,
fingerprint,
pin_code,
verified: is_verified,
},
);
Ok(StatusCode::OK)
}
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(),
}
}