added control panel site using rust

main
Anton Franzluebbers 2022-02-27 16:04:30 -05:00
parent b37edb0222
commit 961a688068
9 changed files with 1728 additions and 2 deletions

3
.gitignore vendored
View File

@ -3,3 +3,6 @@
main
main.exe
main.pdb
.idea/
restarts.log
nohup.out

View File

@ -4,8 +4,19 @@
1. Get a linoox server
2. Clone this repo
3. Install rust: `curl https://sh.rustup.rs -sSf | sh`
4. Set up env: `source $HOME/.cargo/env` or add to `.bashrc`
3. Install rust: `sudo apt install cargo`
5. Build: `cargo build --release`
6. Run: `sudo ./target/release/VelNetServerRust`
7. Or run in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/VelNetServerRust`. You'll have to install `nohup` with apt.
## Running with control panel server
You don't need to do both of these steps. The control panel runs the other server.
1. Get a linoox server
2. Clone this repo
3. Install rust: `sudo apt install cargo`
3. Switch to control panel: `cd control-panel`
5. Build: `cargo build --release`
6. Run the web server in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/control-panel`. You'll have to install `nohup` with apt.

6
control-panel/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
.vscode
main
main.exe
main.pdb
.idea/

1378
control-panel/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
control-panel/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "control-panel"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
handlebars = { version = "4.2.1", features = ["dir_source"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }

View File

@ -0,0 +1,5 @@
{
"port": 8080,
"log_file": "/home/ntsfranz/Documents/VelNetServerRust/nohup.out",
"server_dir": "/home/ntsfranz/Documents/VelNetServerRust/"
}

182
control-panel/src/main.rs Normal file
View File

@ -0,0 +1,182 @@
use actix_web::body::BoxBody;
use actix_web::dev::ServiceResponse;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode;
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{get, web, App, HttpResponse, HttpServer, Result};
use handlebars::Handlebars;
use serde_json::json;
use std::io;
use std::io::{prelude::*, BufReader};
use std::fs;
use std::fs::File;
use std::path::Path;
use serde::{Serialize, Deserialize};
use std::process::{Command, Stdio};
use std::env;
#[derive(Serialize, Deserialize)]
struct Config {
port: u16,
log_file: String,
server_dir: String
}
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
// Macro documentation can be found in the actix_web_codegen crate
#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
//read the log file
let log_file = lines_from_file(config.log_file);
let restarts_log = lines_from_file("../restarts.log");
let uptime = Command::new("uptime")
.output()
.expect("failed to execute process");
let data = json!({
"log_output": log_file,
"restarts_output": restarts_log,
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
});
let body = hb.render("index", &data).unwrap();
HttpResponse::Ok().body(body)
}
#[get("/restart_server")]
async fn restart_server() -> HttpResponse {
// Restart the process
let _output = Command::new("sh")
.arg("../run.sh")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
HttpResponse::Ok().body("DONE")
}
#[get("/git_pull")]
async fn git_pull() -> HttpResponse {
let output = Command::new("git")
.arg("pull")
.output()
.expect("failed to execute process");
HttpResponse::Ok().body(output.stdout)
}
#[get("/compile")]
async fn compile() -> HttpResponse {
//read the config file
let file = fs::read_to_string("config.json").unwrap();
let config: Config = serde_json::from_str(&file).unwrap();
let orig_dir = std::env::current_dir().unwrap();
let root = Path::new(&config.server_dir);
let _new_dir = env::set_current_dir(&root);
let output = Command::new("cargo")
.arg("build")
.arg("--release")
.output()
.expect("failed to execute process");
let _new_dir = env::set_current_dir(orig_dir);
HttpResponse::Ok().body(output.stdout)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
let mut handlebars = Handlebars::new();
handlebars.set_dev_mode(true);
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
let handlebars_ref = web::Data::new(handlebars);
HttpServer::new(move || {
App::new()
.wrap(error_handlers())
.app_data(handlebars_ref.clone())
.service(index)
.service(restart_server)
.service(git_pull)
.service(compile)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
// Custom error handlers, to return HTML responses when an error occurs.
fn error_handlers() -> ErrorHandlers<BoxBody> {
ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
}
// Error handler for a 404 Page not found error.
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
let response = get_error_response(&res, "Page not found");
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
res.into_parts().0,
response.map_into_left_body(),
)))
}
// Generic error handler.
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
let request = res.request();
// Provide a fallback to a simple plain text response in case an error occurs during the
// rendering of the error page.
let fallback = |e: &str| {
HttpResponse::build(res.status())
.content_type(ContentType::plaintext())
.body(e.to_string())
};
let hb = request
.app_data::<web::Data<Handlebars>>()
.map(|t| t.get_ref());
match hb {
Some(hb) => {
let data = json!({
"error": error,
"status_code": res.status().as_str()
});
let body = hb.render("error", &data);
match body {
Ok(body) => HttpResponse::build(res.status())
.content_type(ContentType::html())
.body(body),
Err(_) => fallback(error),
}
}
None => fallback(error),
}
}

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VelNet Control Panel</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<style>
.bottom-scroller {
height: 30em;
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.log-output .panel-block {
padding: .1em .75em;
}
</style>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">
VelNet Control Panel
</h1>
<p class="subtitle">
Log output and utilites for <strong>VelNet</strong>
</p>
</div>
</section>
<section>
<div class="container">
<div class="block">
<button class="button" id="restart-button">Restart Server</button>
<button class="button" id="pull-button">Git Pull</button>
<button class="button" id="compile-button">Compile</button>
</div>
<div class="block">Uptime: {{uptime}}</div>
</div>
</section>
<section class="section">
<div class="container">
<nav class="panel">
<p class="panel-heading">
Server Log
<code>nohup.out</code>
</p>
<div class="content bottom-scroller">
<code class="log-output">
<div>
{{#each log_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav>
<nav class="panel">
<p class="panel-heading">
Server Restarts
<code>restarts.log</code>
</p>
<div class="content bottom-scroller">
<code class="log-output">
<div>
{{#each restarts_output}}
<div class="panel-block">
{{this}}
</div>
{{/each}}
</div>
</code>
</div>
</nav>
</div>
</section>
<script>
"use strict";
document.getElementById('restart-button').addEventListener('click', c=> {
fetch('/restart_server').then(r=> {
setTimeout(location.reload(), 1000);
});
});
document.getElementById('pull-button').addEventListener('click', c=> {
fetch('/git_pull').then(r=> {
location.reload();
});
});
document.getElementById('compile-button').addEventListener('click', c=> {
fetch('/compile').then(r=> {
location.reload();
});
});
</script>
</body>
</html>

13
run.sh Executable file
View File

@ -0,0 +1,13 @@
BASEDIR=$(dirname "$0")
# pushd $BASEDIR
echo $(date +"%Y-%m-%dT%T.%3N%z") >> $BASEDIR/restarts.log
echo "Before: "
pgrep VelNetServer
pkill VelNetServer
echo "\nAfter: "
pgrep VelNetServer
echo "\n"
(cd $BASEDIR && nohup "./target/release/VelNetServerRust" &)
# nohup "$BASEDIR/target/release/VelNetServerRust" &
echo "Starting..." >> $BASEDIR/restarts.log
# popd $BASEDIR