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