wip new console

This commit is contained in:
Luuk van Oijen
2023-11-21 17:24:14 +01:00
parent 67cec26979
commit d9e0761e07
7 changed files with 465 additions and 11 deletions

View File

@@ -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
View 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
}

View File

@@ -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;
},
}
}
}

View File

@@ -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
View 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(())
}
}