commit 7ac1ad269cd16a2c501411e7f51f32501976894e Author: Mateusz Gruszczyński Date: Sat Jul 19 15:51:29 2025 +0200 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b6572df --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +TS3_SERVER=linuxiarz.pl +TS3_QUERY_PORT=10011 +TS3_QUERY_USER=serveradmin +TS3_QUERY_PASS=haslotestowe +RRD_FILE=rrd/linuxiarz.rrd +RRD_UPDATE_INTERVAL=30 + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0f448d6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ts3-manager-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix-web = "4" +tokio = { version = "1", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +rrd = "0.1" # Zakładam dostępność; dostosuj jeśli potrzeba +rand = "0.8" +env_logger = "0.10" +log = "0.4" +serde_teamspeak_querystring = "0.3" # Do parsowania zapytań TS3 +askama = "0.10" # Dla szablonów HTML +askama_derive = "0.10" +chrono = "0.4" +actix-files = "0.6" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2bf83b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM rust:1.70 AS builder +WORKDIR /app +COPY . . +RUN cargo build --release + +FROM debian:buster-slim +WORKDIR /app +COPY --from=builder /app/target/release/ts3-manager-rust /app/ts3-manager-rust +CMD ["/app/ts3-manager-rust"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..50424a8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' +services: + app: + build: . + ports: + - "5000:5000" + environment: + - TS3_SERVER=${TS3_SERVER} + - TS3_QUERY_PORT=${TS3_QUERY_PORT} + - TS3_QUERY_USER=${TS3_QUERY_USER} + - TS3_QUERY_PASS=${TS3_QUERY_PASS} + - RRD_FILE=${RRD_FILE} + - RRD_UPDATE_INTERVAL=${RRD_UPDATE_INTERVAL} + # Dodaj inne zmienne + volumes: + - ./rrd:/app/rrd # Dla trwałości bazy RRD diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..18abb45 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,24 @@ +use std::env; + +pub struct Config { + pub ts3_server: String, + pub ts3_query_port: u16, + pub ts3_query_user: String, + pub ts3_query_pass: String, + pub rrd_file: String, + pub rrd_update_interval: u64, + // Dodaj inne pola z config.example.py[2] +} + +impl Config { + pub fn load() -> Self { + Config { + ts3_server: env::var("TS3_SERVER").unwrap_or("linuxiarz.pl".to_string()), + ts3_query_port: env::var("TS3_QUERY_PORT").unwrap_or("10011".to_string()).parse().unwrap(), + ts3_query_user: env::var("TS3_QUERY_USER").unwrap_or("serveradmin".to_string()), + ts3_query_pass: env::var("TS3_QUERY_PASS").unwrap_or("haslotestowe".to_string()), + rrd_file: env::var("RRD_FILE").unwrap_or("rrd/linuxiarz.rrd".to_string()), + rrd_update_interval: env::var("RRD_UPDATE_INTERVAL").unwrap_or("30".to_string()).parse().unwrap(), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9a6b00c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,118 @@ +use actix_web::{web, App, HttpServer, HttpResponse}; +use askama::Template; +use chrono::prelude::*; +use tokio::time::{self, Duration}; +use actix_files as fs; +use serde::Deserialize; + +use crate::config::Config; +use crate::ts3::{get_ts3_connection, create_channel}; +use crate::rrd::{update_rrd_stats, generate_graphs}; + +mod config; +mod ts3; +mod rrd; + +#[derive(Deserialize)] +struct ChannelData { + channel_name: String, + channel_topic: String, + channel_password: String, +} + +#[derive(Template)] +#[template(path = "create.html")] +struct CreateTemplate<'a> { + ts3_server: &'a str, + ts3_server_port: u16, + client_ip: &'a str, + client_uuid: Option<&'a str>, + now: DateTime, + flash_message: Option, +} + +#[derive(Template)] +#[template(path = "stats.html")] +struct StatsTemplate { + graphs: Vec, + last_update: String, + server_name: String, + now: DateTime, +} + +struct Graph { + file: String, + title: String, +} + +async fn create_handler(data: web::Json) -> HttpResponse { + let config = Config::load(); + // Logika tworzenia kanału (uproszczona; połącz z TS3) + let mut stream = match get_ts3_connection(&config) { + Ok(s) => s, + Err(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 { + Ok(_) => Some("Kanał utworzony pomyślnie".to_string()), + Err(e) => Some(format!("Błąd: {}", e)), + }; + + let template = CreateTemplate { + ts3_server: &config.ts3_server, + ts3_server_port: config.ts3_query_port, // Dostosuj jeśli potrzeba + client_ip: "127.0.0.1", // Pobierz z request (np. req.connection_info().realip_remote_addr()) + client_uuid: Some("example-uuid"), + now: Utc::now(), + flash_message: flash, + }; + HttpResponse::Ok().content_type("text/html").body(template.render().unwrap()) +} + +async fn stats_handler() -> HttpResponse { + let config = Config::load(); + // Generuj wykresy (uproszczone) + generate_graphs(&config); + let graphs = vec![ + 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: "72h.gif".to_string(), title: "Ostatnie 72 godziny".to_string() }, + Graph { file: "week.gif".to_string(), title: "Ostatni tydzień".to_string() }, + ]; + let template = StatsTemplate { + graphs, + last_update: Utc::now().to_string(), + server_name: "linuxiarz.pl".to_string(), + now: Utc::now(), + }; + HttpResponse::Ok().content_type("text/html").body(template.render().unwrap()) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + let config = Config::load(); + + // Tło do aktualizacji RRD + let config_clone = config.clone(); + tokio::spawn(async move { + let mut interval = time::interval(Duration::from_secs(config_clone.rrd_update_interval)); + loop { + interval.tick().await; + update_rrd_stats(&config_clone); + generate_graphs(&config_clone); + } + }); + + HttpServer::new(|| { + App::new() + .service(fs::Files::new("/static", "./static").show_files_listing()) + .route("/create", web::post().to(create_handler)) + .route("/stats", web::get().to(stats_handler)) + }) + .bind(("0.0.0.0", 5000))? + .run() + .await +} diff --git a/src/rrd.rs b/src/rrd.rs new file mode 100644 index 0000000..f6f549b --- /dev/null +++ b/src/rrd.rs @@ -0,0 +1,12 @@ +use crate::config::Config; // Import Config +// Zakładam użycie biblioteki rrd; dostosuj + +pub fn update_rrd_stats(config: &Config) { + // Pobierz dane z TS3 i zaktualizuj RRD (na podstawie app.py[1]) + log::info!("Updating RRD stats"); +} + +pub fn generate_graphs(config: &Config) { + // Generuj wykresy (na podstawie create_rrd_graph w app.py[1]) + log::info!("Generating graphs"); +} diff --git a/src/ts3.rs b/src/ts3.rs new file mode 100644 index 0000000..609c861 --- /dev/null +++ b/src/ts3.rs @@ -0,0 +1,20 @@ +use std::net::TcpStream; +use std::io::{BufRead, BufReader, Write}; +use crate::config::Config; // Import Config + +pub fn get_ts3_connection(config: &Config) -> Result { + let mut stream = TcpStream::connect(format!("{}:{}", config.ts3_server, config.ts3_query_port)) + .map_err(|e| e.to_string())?; + let mut writer = stream.try_clone().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 + Ok(stream) +} + +pub fn create_channel(stream: &mut TcpStream, name: &str, topic: &str, password: &str) -> Result { + // Implementacja channelcreate, etc. (uproszczona; dostosuj na podstawie app.py[1]) + let command = format!("channelcreate channel_name={} channel_topic={} channel_password={}\n", name, topic, password); + stream.write_all(command.as_bytes()).unwrap(); + // Parsuj odpowiedź (użyj serde_teamspeak_querystring) + unimplemented!(); // Pełna implementacja wymaga testów +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..5d27a0a --- /dev/null +++ b/static/style.css @@ -0,0 +1,33 @@ +:root { + --ts-blue: #2580c3; /* Główny niebieski TS3 [10] */ + --ts-dark: #1c2537; /* Ciemne tło [10] */ + --ts-green: #25dd85; /* Akcent zielony [10] */ + --ts-light: #e0e0e0; /* Jasny tekst [10] */ +} + +body { + background-color: var(--ts-dark); + color: var(--ts-light); +} + +.navbar { + background-color: var(--ts-blue) !important; +} + +.btn-primary { + background-color: var(--ts-green); + border-color: var(--ts-green); +} + +.card { + background-color: rgba(0, 0, 0, 0.5); /* Półprzezroczyste dla dark mode [8] */ + border: 1px solid var(--ts-blue); +} + +.card-title, .card-text { + color: var(--ts-light); +} + +img.graph { + border: 2px solid var(--ts-green); /* Ramka dla wykresów jak w TS3 [14] */ +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..7186ad2 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,28 @@ + + + + {% block title %}{% endblock %} + + + + + +
+ {% block content %}{% endblock %} +
+
+ TS3 Manager © {{ now.year }} | Hosted by linuxiarz.pl +
+ + + diff --git a/templates/create.html b/templates/create.html new file mode 100644 index 0000000..7c85547 --- /dev/null +++ b/templates/create.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% block title %}Utwórz kanał | linuxiarz.pl{% endblock %} +{% block content %} +
+
+
Utwórz swój kanał
+

Połącz z serwerem: {{ ts3_server }}:{{ ts3_server_port }}

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ {% if flash_message %}
{{ flash_message }}
{% endif %} +

Twój IP: {{ client_ip }} | UUID: {{ client_uuid }} | Data: {{ now }}

+
+
+{% endblock %} diff --git a/templates/stats.html b/templates/stats.html new file mode 100644 index 0000000..53747e5 --- /dev/null +++ b/templates/stats.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block title %}Statystyki serwera | linuxiarz.pl{% endblock %} +{% block content %} +
+
+
Statystyki użytkowników
+

Ostatnia aktualizacja: {{ last_update }}

+

Serwer: {{ server_name }}

+ {% for graph in graphs %} +
{{ graph.title }}
+ {{ graph.title }} + {% endfor %} +

Aktualny czas: {{ now }}

+
+
+{% endblock %}