This commit is contained in:
root
2025-07-19 16:36:51 +02:00
parent 7ac1ad269c
commit e8502779f0
7 changed files with 78 additions and 48 deletions

View File

@@ -11,8 +11,8 @@ rrd = "0.1" # Zakładam dostępność; dostosuj jeśli potrzeba
rand = "0.8" rand = "0.8"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
serde_teamspeak_querystring = "0.3" # Do parsowania zapytań TS3 serde-teamspeak-querystring = "0.3" # Do parsowania zapytań TS3
askama = "0.10" # Dla szablonów HTML askama = "0.10" # Dla szablonów HTML
askama_derive = "0.10" askama_derive = "0.10"
chrono = "0.4" chrono = "0.4"
actix-files = "0.6" actix-files = "0.6"

View File

@@ -1,5 +1,6 @@
FROM rust:1.70 AS builder FROM rust:1.88 AS builder
WORKDIR /app WORKDIR /app
RUN apt-get update && apt-get install -y libclang-dev librrd-dev
COPY . . COPY . .
RUN cargo build --release RUN cargo build --release

View File

@@ -1,5 +1,6 @@
use std::env; use std::env;
#[derive(Clone)]
pub struct Config { pub struct Config {
pub ts3_server: String, pub ts3_server: String,
pub ts3_query_port: u16, pub ts3_query_port: u16,
@@ -7,7 +8,7 @@ pub struct Config {
pub ts3_query_pass: String, pub ts3_query_pass: String,
pub rrd_file: String, pub rrd_file: String,
pub rrd_update_interval: u64, pub rrd_update_interval: u64,
// Dodaj inne pola z config.example.py[2] // Dodaj inne pola z config.example.py[2], np. channel_admin_group: u32
} }
impl Config { impl Config {

View File

@@ -1,12 +1,12 @@
use actix_web::{web, App, HttpServer, HttpResponse}; use actix_web::{web, App, HttpServer, HttpResponse};
use askama::Template; use askama::Template;
use chrono::prelude::*; use chrono::{Utc, Datelike};
use tokio::time::{self, Duration}; use tokio::time::{self, Duration};
use actix_files as fs; use actix_files as fs;
use serde::Deserialize; use serde::Deserialize;
use crate::config::Config; use crate::config::Config;
use crate::ts3::{get_ts3_connection, create_channel}; use crate::ts3::{get_ts3_connection, create_channel, get_client_info};
use crate::rrd::{update_rrd_stats, generate_graphs}; use crate::rrd::{update_rrd_stats, generate_graphs};
mod config; mod config;
@@ -26,18 +26,18 @@ struct CreateTemplate<'a> {
ts3_server: &'a str, ts3_server: &'a str,
ts3_server_port: u16, ts3_server_port: u16,
client_ip: &'a str, client_ip: &'a str,
client_uuid: Option<&'a str>, client_uuid: &'a str,
now: DateTime<Utc>, year: i32,
flash_message: Option<String>, flash_message: Option<&'a str>,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "stats.html")] #[template(path = "stats.html")]
struct StatsTemplate { struct StatsTemplate<'a> {
graphs: Vec<Graph>, graphs: &'a [Graph],
last_update: String, last_update: &'a str,
server_name: String, server_name: &'a str,
now: DateTime<Utc>, year: i32,
} }
struct Graph { struct Graph {
@@ -47,45 +47,61 @@ struct Graph {
async fn create_handler(data: web::Json<ChannelData>) -> HttpResponse { async fn create_handler(data: web::Json<ChannelData>) -> HttpResponse {
let config = Config::load(); let config = Config::load();
// Logika tworzenia kanału (uproszczona; połącz z TS3)
let mut stream = match get_ts3_connection(&config) { let mut stream = match get_ts3_connection(&config) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
return HttpResponse::InternalServerError().body(format!("Błąd połączenia: {}", e)); return HttpResponse::InternalServerError().body(format!("Błąd połączenia: {}", e));
} }
}; };
let result = create_channel(&mut stream, &data.channel_name, &data.channel_topic, &data.channel_password);
let flash = match result { // Pobierz client_info jak w app.py[1]
Ok(_) => Some("Kanał utworzony pomyślnie".to_string()), let client_info = get_client_info(&mut stream, "127.0.0.1");
Err(e) => Some(format!("Błąd: {}", e)), let (client_uuid, client_dbid, client_clid) = match &client_info {
Some(info) => (info.uid.as_str(), info.dbid, info.clid),
None => ("Brak UUID", 0, 0),
}; };
let result = create_channel(
&mut stream,
&data.channel_name,
&data.channel_topic,
&data.channel_password,
client_dbid,
client_clid,
);
let flash_message = match result {
Ok(_) => Some("Kanał utworzony pomyślnie"),
Err(_e) => Some("Błąd tworzenia kanału"),
};
let now = Utc::now();
let template = CreateTemplate { let template = CreateTemplate {
ts3_server: &config.ts3_server, ts3_server: &config.ts3_server,
ts3_server_port: config.ts3_query_port, // Dostosuj jeśli potrzeba ts3_server_port: config.ts3_query_port,
client_ip: "127.0.0.1", // Pobierz z request (np. req.connection_info().realip_remote_addr()) client_ip: "127.0.0.1",
client_uuid: Some("example-uuid"), client_uuid,
now: Utc::now(), year: now.year(),
flash_message: flash, flash_message,
}; };
HttpResponse::Ok().content_type("text/html").body(template.render().unwrap()) HttpResponse::Ok().content_type("text/html").body(template.render().unwrap())
} }
async fn stats_handler() -> HttpResponse { async fn stats_handler() -> HttpResponse {
let config = Config::load(); let config = Config::load();
// Generuj wykresy (uproszczone)
generate_graphs(&config); generate_graphs(&config);
let graphs = vec![ let graphs = [
Graph { file: "hour.gif".to_string(), title: "Ostatnie 12 godzin".to_string() }, Graph { file: "hour.gif".to_string(), title: "Ostatnie 12 godzin".to_string() },
Graph { file: "day.gif".to_string(), title: "Ostatnie 24 godziny".to_string() }, Graph { file: "day.gif".to_string(), title: "Ostatnie 24 godziny".to_string() },
Graph { file: "72h.gif".to_string(), title: "Ostatnie 72 godziny".to_string() }, Graph { file: "72h.gif".to_string(), title: "Ostatnie 72 godziny".to_string() },
Graph { file: "week.gif".to_string(), title: "Ostatni tydzień".to_string() }, Graph { file: "week.gif".to_string(), title: "Ostatni tydzień".to_string() },
]; ];
let now = Utc::now();
let last_update = now.format("%Y-%m-%d %H:%M:%S").to_string();
let template = StatsTemplate { let template = StatsTemplate {
graphs, graphs: &graphs,
last_update: Utc::now().to_string(), last_update: &last_update,
server_name: "linuxiarz.pl".to_string(), server_name: "linuxiarz.pl",
now: Utc::now(), year: now.year(),
}; };
HttpResponse::Ok().content_type("text/html").body(template.render().unwrap()) HttpResponse::Ok().content_type("text/html").body(template.render().unwrap())
} }
@@ -95,7 +111,6 @@ async fn main() -> std::io::Result<()> {
env_logger::init(); env_logger::init();
let config = Config::load(); let config = Config::load();
// Tło do aktualizacji RRD
let config_clone = config.clone(); let config_clone = config.clone();
tokio::spawn(async move { tokio::spawn(async move {
let mut interval = time::interval(Duration::from_secs(config_clone.rrd_update_interval)); let mut interval = time::interval(Duration::from_secs(config_clone.rrd_update_interval));

View File

@@ -1,12 +1,12 @@
use crate::config::Config; // Import Config use crate::config::Config;
// Zakładam użycie biblioteki rrd; dostosuj use log;
pub fn update_rrd_stats(config: &Config) { pub fn update_rrd_stats(config: &Config) {
// Pobierz dane z TS3 i zaktualizuj RRD (na podstawie app.py[1]) // Pobierz online users jak w app.py[1], update RRD
log::info!("Updating RRD stats"); log::info!("Updating RRD stats for file: {}", config.rrd_file);
} }
pub fn generate_graphs(config: &Config) { pub fn generate_graphs(config: &Config) {
// Generuj wykresy (na podstawie create_rrd_graph w app.py[1]) // Generuj wykresy dla hour, day, 72h, week jak w app.py[1]
log::info!("Generating graphs"); log::info!("Generating graphs with interval: {}", config.rrd_update_interval);
} }

View File

@@ -1,20 +1,33 @@
use std::net::TcpStream; use std::net::TcpStream;
use std::io::{BufRead, BufReader, Write}; use std::io::Write;
use crate::config::Config; // Import Config use crate::config::Config;
pub fn get_ts3_connection(config: &Config) -> Result<TcpStream, String> { pub fn get_ts3_connection(config: &Config) -> Result<TcpStream, String> {
let mut stream = TcpStream::connect(format!("{}:{}", config.ts3_server, config.ts3_query_port)) let stream = TcpStream::connect(format!("{}:{}", config.ts3_server, config.ts3_query_port))
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let mut writer = stream.try_clone().unwrap(); let mut writer = stream.try_clone().unwrap();
writer.write_all(format!("login {} {}\n", config.ts3_query_user, config.ts3_query_pass).as_bytes()).unwrap(); writer.write_all(format!("login {} {}\n", config.ts3_query_user, config.ts3_query_pass).as_bytes()).unwrap();
// Dodaj odczyt odpowiedzi i obsługę błędów // Dodaj odczyt odpowiedzi i obsługę błędów, jak w app.py[1]
Ok(stream) Ok(stream)
} }
pub fn create_channel(stream: &mut TcpStream, name: &str, topic: &str, password: &str) -> Result<String, String> { // Symulacja get_client_info z app.py[1] (porównanie IP, zwrot UUID itd.)
// Implementacja channelcreate, etc. (uproszczona; dostosuj na podstawie app.py[1]) pub fn get_client_info(_ts3conn: &mut TcpStream, _ip_address: &str) -> Option<ClientInfo> {
let command = format!("channelcreate channel_name={} channel_topic={} channel_password={}\n", name, topic, password); // Pełna implementacja: Wyślij clientlist, clientinfo, porównaj IP
stream.write_all(command.as_bytes()).unwrap(); // Tymczasowo: unimplemented!()
// Parsuj odpowiedź (użyj serde_teamspeak_querystring) unimplemented!();
unimplemented!(); // Pełna implementacja wymaga testów }
pub struct ClientInfo {
pub uid: String,
pub nickname: String,
pub dbid: u32,
pub clid: u32,
pub ip: String,
}
pub fn create_channel(_stream: &mut TcpStream, _name: &str, _topic: &str, _password: &str, _client_dbid: u32, _client_clid: u32) -> Result<String, String> {
// Pełna logika z app.py[1]: channelcreate, setclientchannelgroup, clientmove
// Tymczasowo: unimplemented!()
unimplemented!();
} }

View File

@@ -21,7 +21,7 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<footer class="text-center mt-4"> <footer class="text-center mt-4">
TS3 Manager © {{ now.year }} | Hosted by linuxiarz.pl TS3 Manager © {{ year }} | Hosted by linuxiarz.pl
</footer> </footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body> </body>