mirror of
https://github.com/edu4rdshl/webtail-rs.git
synced 2026-07-17 23:24:50 +00:00
Initial commit.
Signed-off-by: Edu4rdSHL <edu4rdshl@protonmail.com>
This commit is contained in:
parent
f9c5631d6d
commit
23f888270e
14 changed files with 1511 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
*.txt
|
||||||
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug",
|
||||||
|
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
|
||||||
|
"args": "-f slack3",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"sourceLanguages": [
|
||||||
|
"rust"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo build",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1206
Cargo.lock
generated
Normal file
1206
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "webtail-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Edu4rdSHL <edu4rdshl@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { version = "0.4.10", features = ["sse"] }
|
||||||
|
rocket_contrib = "0.4.7"
|
||||||
|
clap = { version = "2.33.3", features = ["yaml"] }
|
||||||
|
rayon = "1.5.1"
|
||||||
2
examples/webtail.conf
Normal file
2
examples/webtail.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
file1.txt:8080
|
||||||
|
file2.txt:9090
|
||||||
18
src/args.rs
Normal file
18
src/args.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use {
|
||||||
|
crate::structs::Args,
|
||||||
|
clap::{load_yaml, value_t, App},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
pub fn get_args() -> Args {
|
||||||
|
let yaml = load_yaml!("cli.yml");
|
||||||
|
let matches = App::from_yaml(yaml)
|
||||||
|
.version(clap::crate_version!())
|
||||||
|
.get_matches();
|
||||||
|
Args {
|
||||||
|
file_name: value_t!(matches.value_of("file"), String).unwrap_or_default(),
|
||||||
|
delay: value_t!(matches.value_of("delay"), u64).unwrap_or(1),
|
||||||
|
port: value_t!(matches.value_of("port"), u16).unwrap_or(8080),
|
||||||
|
config_file: value_t!(matches.value_of("config"), String).unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/cli.yml
Normal file
34
src/cli.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
name: webtail-rs
|
||||||
|
author: Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||||
|
settings:
|
||||||
|
- ArgRequiredElseHelp
|
||||||
|
- StrictUtf8
|
||||||
|
about: tail -f for your browser.
|
||||||
|
args:
|
||||||
|
- file:
|
||||||
|
short: f
|
||||||
|
long: file
|
||||||
|
help: File to follow.
|
||||||
|
takes_value: true
|
||||||
|
multiple: false
|
||||||
|
|
||||||
|
- delay:
|
||||||
|
short: d
|
||||||
|
long: delay
|
||||||
|
help: Time between reads.
|
||||||
|
takes_value: true
|
||||||
|
multiple: false
|
||||||
|
|
||||||
|
- port:
|
||||||
|
short: p
|
||||||
|
long: port
|
||||||
|
help: Port to use.
|
||||||
|
takes_value: true
|
||||||
|
multiple: false
|
||||||
|
|
||||||
|
- config:
|
||||||
|
short: c
|
||||||
|
long: config
|
||||||
|
help: Config file to use. You can use the path:port syntax for viewing multiple files.
|
||||||
|
takes_value: true
|
||||||
|
multiple: false
|
||||||
6
src/lib.rs
Normal file
6
src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
|
|
||||||
|
pub mod args;
|
||||||
|
pub mod structs;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod worker;
|
||||||
58
src/main.rs
Normal file
58
src/main.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use std::{collections::HashMap, u16};
|
||||||
|
|
||||||
|
use webtail_rs::{
|
||||||
|
structs::{File, State},
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
use {
|
||||||
|
rayon::prelude::*,
|
||||||
|
rocket::{
|
||||||
|
config::{Config, Environment, LoggingLevel},
|
||||||
|
routes,
|
||||||
|
},
|
||||||
|
rocket_contrib::serve::StaticFiles,
|
||||||
|
webtail_rs::{args, worker::*},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = args::get_args();
|
||||||
|
|
||||||
|
if args.file_name.is_empty() && args.config_file.is_empty() {
|
||||||
|
eprintln!("No files to process.");
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input_files: HashMap<String, u16> = HashMap::new();
|
||||||
|
|
||||||
|
if !args.config_file.is_empty() {
|
||||||
|
let buffer = utils::return_bufreader(&args.config_file);
|
||||||
|
input_files = utils::read_config_file(buffer);
|
||||||
|
}
|
||||||
|
if !args.file_name.is_empty() {
|
||||||
|
input_files.insert(args.file_name.clone(), args.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
input_files
|
||||||
|
.par_iter()
|
||||||
|
.map(|(file, port)| {
|
||||||
|
println!("=> Watching file {} in port {}\n", file, port);
|
||||||
|
let error = rocket::custom(
|
||||||
|
Config::build(Environment::Production)
|
||||||
|
.log_level(LoggingLevel::Off)
|
||||||
|
.port(port.to_owned())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.manage(File {
|
||||||
|
data: String::new(),
|
||||||
|
name: file.clone(),
|
||||||
|
state: State::Write,
|
||||||
|
delay: args.delay,
|
||||||
|
})
|
||||||
|
.mount("/", routes![updates])
|
||||||
|
.mount("/", StaticFiles::from("static"))
|
||||||
|
.launch();
|
||||||
|
eprintln!("Launch failed! Error: {}", error);
|
||||||
|
})
|
||||||
|
.collect::<Vec<()>>();
|
||||||
|
}
|
||||||
21
src/structs.rs
Normal file
21
src/structs.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Args {
|
||||||
|
pub file_name: String,
|
||||||
|
pub delay: u64,
|
||||||
|
pub port: u16,
|
||||||
|
pub config_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct File {
|
||||||
|
pub data: String,
|
||||||
|
pub state: State,
|
||||||
|
pub name: String,
|
||||||
|
pub delay: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum State {
|
||||||
|
Flush,
|
||||||
|
Sleep,
|
||||||
|
Write,
|
||||||
|
}
|
||||||
32
src/utils.rs
Normal file
32
src/utils.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn return_bufreader(file: &str) -> BufReader<File> {
|
||||||
|
match std::fs::File::open(file) {
|
||||||
|
Ok(file) => BufReader::new(file),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error reading the file: {}. Error: {}", file, e);
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_config_file(buffer: BufReader<File>) -> HashMap<String, u16> {
|
||||||
|
let mut data: HashMap<String, u16> = HashMap::new();
|
||||||
|
for line in buffer.lines() {
|
||||||
|
let line_str = line.expect("Error");
|
||||||
|
let data_vec = line_str.split(':').collect::<Vec<&str>>();
|
||||||
|
if data_vec.len() == 2 {
|
||||||
|
data.insert(
|
||||||
|
data_vec.get(0).unwrap().to_string(),
|
||||||
|
data_vec.get(1).unwrap().parse().unwrap_or_else(|_| {
|
||||||
|
panic!("Error reading the port for the line {:?}", data_vec)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
61
src/worker.rs
Normal file
61
src/worker.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use rocket::response::{Content, Stream};
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
structs::{File, State},
|
||||||
|
utils,
|
||||||
|
},
|
||||||
|
rocket::{get, http::ContentType},
|
||||||
|
std::{
|
||||||
|
io::{self, prelude::*, BufReader, Read, Write},
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BUF_SIZE: usize = 52428800;
|
||||||
|
|
||||||
|
impl Read for File {
|
||||||
|
fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.state {
|
||||||
|
State::Flush => {
|
||||||
|
self.state = State::Sleep;
|
||||||
|
return Err(io::ErrorKind::WouldBlock.into());
|
||||||
|
}
|
||||||
|
State::Sleep => std::thread::sleep(Duration::from_secs(self.delay)),
|
||||||
|
State::Write => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = State::Flush;
|
||||||
|
|
||||||
|
self.data.clear();
|
||||||
|
|
||||||
|
self.data += &format!("data: Watching file {}\ndata: \n", self.name);
|
||||||
|
|
||||||
|
for line in utils::return_bufreader(&self.name).lines() {
|
||||||
|
self.data += &format!("data: {}\n", line.expect("Error"))
|
||||||
|
}
|
||||||
|
self.data += "\n\n";
|
||||||
|
|
||||||
|
buf.write_all(self.data.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(self.data.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CounterStream = Stream<BufReader<File>>;
|
||||||
|
|
||||||
|
#[get("/updates")]
|
||||||
|
pub fn updates(file_data: rocket::State<File>) -> Content<CounterStream> {
|
||||||
|
let reader = BufReader::with_capacity(
|
||||||
|
BUF_SIZE,
|
||||||
|
File {
|
||||||
|
data: file_data.data.clone(),
|
||||||
|
state: file_data.state,
|
||||||
|
name: file_data.name.clone(),
|
||||||
|
delay: file_data.delay,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let ct = ContentType::with_params("text", "event-stream", ("charset", "utf-8"));
|
||||||
|
Content(ct, Stream::from(reader))
|
||||||
|
}
|
||||||
24
static/index.html
Normal file
24
static/index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>Webtail RS</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="spong">Loading...</div>
|
||||||
|
</body>
|
||||||
|
<script charset="utf-8">
|
||||||
|
status_node = document.getElementById('spong');
|
||||||
|
status_node.innerText = 'Waiting...';
|
||||||
|
|
||||||
|
es = new EventSource("updates");
|
||||||
|
es.onmessage = function (event) {
|
||||||
|
console.log(event);
|
||||||
|
status_node.innerText = event.data;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue