mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-04-05 15:26:19 +00:00
wip new console
This commit is contained in:
@@ -47,7 +47,7 @@ pub async fn backend_heartbeat(config: std::sync::Arc<crate::config::Config>, mu
|
||||
if let Some(status) = status {
|
||||
trace!("status update: {:?}", status);
|
||||
info.players = status.player_count;
|
||||
info.playerslist = status.player_list.clone();
|
||||
info.playerslist = status.player_list.iter().map(|(_id, name)| format!("{};", name)).collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ async fn heartbeat_post(heartbeat_info: &HeartbeatInfo) {
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
debug!("heartbeat response:\n{:?}", resp.text().await);
|
||||
trace!("heartbeat response:\n{:?}", resp.text().await);
|
||||
},
|
||||
Err(e) => error!("Heartbeat error occured: {e}"),
|
||||
}
|
||||
|
||||
38
src/logger.rs
Normal file
38
src/logger.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use log::{Record, Level, Metadata, SetLoggerError, LevelFilter};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref LOG_BUFFER: Mutex<Vec<(Level, String)>> = Mutex::new(Vec::new());
|
||||
}
|
||||
|
||||
struct BufferedLogger;
|
||||
|
||||
impl log::Log for BufferedLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let msg = format!("{}", record.args());
|
||||
let mut lock = LOG_BUFFER.lock().expect("Logging lock poisoned!");
|
||||
lock.push((record.level(), msg));
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
static LOGGER: BufferedLogger = BufferedLogger;
|
||||
|
||||
pub fn init(filter: LevelFilter) -> Result<(), SetLoggerError> {
|
||||
log::set_logger(&LOGGER)
|
||||
.map(|()| log::set_max_level(filter))
|
||||
}
|
||||
|
||||
pub async fn drain_log_buffer() -> Vec<(Level, String)> {
|
||||
let mut lock = LOG_BUFFER.lock().expect("Logging lock poisoned!");
|
||||
let v = lock.drain(..).collect();
|
||||
drop(lock);
|
||||
v
|
||||
}
|
||||
37
src/main.rs
37
src/main.rs
@@ -5,6 +5,8 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod logger;
|
||||
mod tui;
|
||||
mod server;
|
||||
mod config;
|
||||
mod heartbeat;
|
||||
@@ -12,12 +14,13 @@ mod heartbeat;
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::max()).init();
|
||||
pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Debug).init();
|
||||
// pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Debug).init();
|
||||
logger::init(log::LevelFilter::max()).expect("Failed to enable logger!");
|
||||
|
||||
let mut user_config: config::Config = toml::from_str(
|
||||
&std::fs::read_to_string("ServerConfig.toml")
|
||||
.map_err(|_| error!("Failed to read config file!"))
|
||||
.expect("Failed to read config file!")
|
||||
&std::fs::read_to_string("ServerConfig.toml")
|
||||
.map_err(|_| error!("Failed to read config file!"))
|
||||
.expect("Failed to read config file!")
|
||||
)
|
||||
.map_err(|_| error!("Failed to parse config file!"))
|
||||
.expect("Failed to parse config file!");
|
||||
@@ -43,10 +46,14 @@ async fn main() {
|
||||
|
||||
let user_config = Arc::new(user_config);
|
||||
|
||||
server_main(user_config).await;
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel(100);
|
||||
|
||||
tokio::spawn(tui::tui_main(user_config.clone(), cmd_tx));
|
||||
|
||||
server_main(user_config, cmd_rx).await;
|
||||
}
|
||||
|
||||
async fn server_main(user_config: Arc<config::Config>) {
|
||||
async fn server_main(user_config: Arc<config::Config>, mut cmd_rx: mpsc::Receiver<Vec<String>>) {
|
||||
let (hb_tx, hb_rx) = mpsc::channel(100);
|
||||
|
||||
tokio::spawn(heartbeat::backend_heartbeat(user_config.clone(), hb_rx));
|
||||
@@ -89,9 +96,25 @@ async fn server_main(user_config: Arc<config::Config>) {
|
||||
let new_status = server.get_server_status();
|
||||
|
||||
if status != new_status {
|
||||
trace!("WHAT");
|
||||
status = new_status;
|
||||
hb_tx.send(status.clone()).await;
|
||||
}
|
||||
|
||||
// Process commands
|
||||
match cmd_rx.try_recv() {
|
||||
Ok(cmd) => if cmd.len() > 0 {
|
||||
match cmd[0].as_str() {
|
||||
"exit" => break,
|
||||
_ => info!("Unknown command!"),
|
||||
}
|
||||
} else {
|
||||
// what!
|
||||
},
|
||||
Err(mpsc::error::TryRecvError::Empty) => {},
|
||||
Err(e) => {
|
||||
error!("Error: {e}");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ fn load_plugins() -> Vec<Plugin> {
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct ServerStatus {
|
||||
pub player_count: usize,
|
||||
pub player_list: String,
|
||||
pub player_list: Vec<(u8, String)>,
|
||||
pub max_players: usize,
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ impl Server {
|
||||
ServerStatus {
|
||||
player_count: self.clients.len(),
|
||||
player_list: self.clients.iter().map(|client| {
|
||||
format!("{};", &client.get_name())
|
||||
(client.id, client.get_name().to_string())
|
||||
}).collect(),
|
||||
// max_players: self.max_players, // TODO: Support this
|
||||
max_players: self.config.general.max_players,
|
||||
|
||||
145
src/tui.rs
Normal file
145
src/tui.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::sync::Arc;
|
||||
use std::io::{stdout, Result};
|
||||
use std::collections::VecDeque;
|
||||
use log::Level;
|
||||
|
||||
use crossterm::{
|
||||
event::{self, KeyCode, KeyEventKind},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{Paragraph, Block, Borders},
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn tui_main(config: Arc<Config>, cmd_tx: mpsc::Sender<Vec<String>>) {
|
||||
let mut tui = Tui::new().expect("Failed to initialize tui!");
|
||||
|
||||
'app: loop {
|
||||
if tui.update().await.expect("Failed to run tui.update!") {
|
||||
cmd_tx.send(vec!["exit".to_string()]).await.unwrap();
|
||||
break 'app;
|
||||
}
|
||||
tui.draw().expect("Failed to run tui.draw!");
|
||||
}
|
||||
}
|
||||
|
||||
struct Tui {
|
||||
terminal: Terminal<CrosstermBackend<std::io::Stdout>>,
|
||||
|
||||
log_buffer: VecDeque<(Level, String)>,
|
||||
|
||||
input: String,
|
||||
}
|
||||
|
||||
impl Drop for Tui {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.cleanup() {
|
||||
eprintln!("Error occured related to the TUI: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
fn new() -> Result<Self> {
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
|
||||
Ok(Self {
|
||||
terminal,
|
||||
|
||||
log_buffer: VecDeque::new(),
|
||||
|
||||
input: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn cleanup(&self) -> Result<()> {
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update(&mut self) -> Result<bool> {
|
||||
for (level, msg) in crate::logger::drain_log_buffer().await {
|
||||
self.log_buffer.push_front((level, msg));
|
||||
if self.log_buffer.len() > 100 {
|
||||
self.log_buffer.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
if let event::Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn draw(&mut self) -> Result<()> {
|
||||
self.terminal.draw(|frame| {
|
||||
let area = frame.size();
|
||||
|
||||
let vert_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![
|
||||
Constraint::Length(area.height - 3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let horiz_layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Percentage(70),
|
||||
Constraint::Percentage(30),
|
||||
])
|
||||
.split(vert_layout[0]);
|
||||
|
||||
let mut lines = Vec::new();
|
||||
for (i, (level, msg)) in self.log_buffer.iter().enumerate() {
|
||||
if i >= (area.height as usize - 5) { break; }
|
||||
let level_style = match level {
|
||||
Level::Info => Style::default().green(),
|
||||
Level::Warn => Style::default().yellow(),
|
||||
Level::Error => Style::default().red(),
|
||||
Level::Debug => Style::default().gray(),
|
||||
Level::Trace => Style::default().cyan(),
|
||||
};
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled("[", Style::default()),
|
||||
Span::styled(format!("{}", level), level_style),
|
||||
Span::styled(format!("] {}", msg), Style::default()),
|
||||
]));
|
||||
}
|
||||
lines.reverse();
|
||||
frame.render_widget(
|
||||
// Paragraph::new("[INFO] info!!!\n[DEBUG] debug!!!!").block(Block::new().borders(Borders::ALL)),
|
||||
Paragraph::new(Text::from(lines)).block(Block::new().borders(Borders::ALL)),
|
||||
horiz_layout[0],
|
||||
);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new("0 - luuk-bepis\n1 - lion guy\n2 - the_racc").block(Block::new().borders(Borders::ALL)),
|
||||
horiz_layout[1],
|
||||
);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!(" > {}", self.input)).block(Block::new().borders(Borders::ALL)),
|
||||
vert_layout[1],
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user