Added Additional features
Postgres database and diesel was added History capability has been added Added additional handlebars pages to view and submit get/post requests via form.
This commit is contained in:
commit
e02426797d
|
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=postgres://username:password@localhost/request_mirror_db
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.env.local
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
// 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": "Release",
|
||||
"program": "${workspaceFolder}/target/release/request-mirror",
|
||||
"args": ["--release"],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/target/debug/request-mirror",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "request-mirror"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.34"
|
||||
diesel = { version = "2.1.4", features = ["postgres", "chrono"] }
|
||||
dotenvy = "0.15.7"
|
||||
rocket = "0.4.10"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
version = "0.4.10"
|
||||
features = ["handlebars_templates", "tera_templates"]
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.7.0"
|
||||
features = [
|
||||
"v4", # Lets you generate random UUIDs
|
||||
"fast-rng", # Use a faster (but still sufficiently random) RNG
|
||||
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
|
||||
]
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE IF EXISTS clients;
|
||||
DROP TABLE IF EXISTS ownership;
|
||||
DROP TABLE IF EXISTS history;
|
||||
DROP TABLE IF EXISTS pair_records;
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE IF NOT EXISTS clients
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
ip TEXT NOT NULL,
|
||||
mirror_id TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ownership
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
owner_id TEXT NOT NULL,
|
||||
client_id TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS history
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
client_id TEXT NOT NULL,
|
||||
request_type TEXT NOT NULL,
|
||||
timestamp TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pair_records
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
history_id INTEGER NOT NULL,
|
||||
pair_type INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
pub mod models;
|
||||
pub mod schema;
|
||||
|
||||
use chrono::offset::Utc;
|
||||
use diesel::prelude::*;
|
||||
use dotenvy::dotenv;
|
||||
use models::{
|
||||
Client, NewClient, NewHistoryRecord, NewPairRecord
|
||||
};
|
||||
use schema::{
|
||||
clients, history, pair_records
|
||||
};
|
||||
use std::env;
|
||||
|
||||
pub fn establish_connection() -> PgConnection {
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
PgConnection::establish(&database_url)
|
||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||
}
|
||||
|
||||
pub fn create_client(conn: &mut PgConnection, ip: &str, mirror_id: &str) -> usize {
|
||||
let new_client = NewClient {ip: &ip, mirror_id: &mirror_id};
|
||||
|
||||
diesel::insert_into(clients::table)
|
||||
.values(&new_client)
|
||||
.returning(Client::as_returning())
|
||||
.execute(conn)
|
||||
.expect("Error")
|
||||
}
|
||||
|
||||
pub fn create_history_record(conn: &mut PgConnection, client_id: &str, request_type: &str, ) -> i32 {
|
||||
let new_history_record = NewHistoryRecord {
|
||||
client_id: &client_id,
|
||||
request_type: request_type,
|
||||
timestamp: Utc::now().naive_utc()
|
||||
};
|
||||
|
||||
diesel::insert_into(history::table)
|
||||
.values(&new_history_record)
|
||||
.returning(history::columns::id)
|
||||
//.execute(conn).unwrap()
|
||||
.get_result(conn).unwrap()
|
||||
}
|
||||
|
||||
pub fn create_pair_record(conn: &mut PgConnection, history_id: i32, pair_type: i32, key: &str, value: &str) -> usize {
|
||||
let new_pair_record = NewPairRecord {history_id: history_id, pair_type: pair_type, key: &key, value: &value};
|
||||
|
||||
diesel::insert_into(pair_records::table)
|
||||
.values(&new_pair_record)
|
||||
.execute(conn).unwrap()
|
||||
}
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use request_mirror::schema::{history, pair_records};
|
||||
use rocket::{data, request, Outcome, Request, Data};
|
||||
use rocket::data::FromDataSimple;
|
||||
use rocket::request::FromRequest;
|
||||
use rocket_contrib::templates::Template;
|
||||
use serde::Serialize;
|
||||
use rocket::http::{Cookie, Cookies, Status};
|
||||
use uuid::Uuid;
|
||||
use std::io::Read;
|
||||
use request_mirror::models::*;
|
||||
use diesel::prelude::*;
|
||||
use request_mirror::*;
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
enum PairType {
|
||||
Header,
|
||||
Cookie,
|
||||
Query,
|
||||
Body
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct Pair {
|
||||
key: String,
|
||||
value: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct RequestInfo {
|
||||
header: Vec<Pair>,
|
||||
cookies: Vec<Pair>,
|
||||
query: Vec<Pair>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct RequestBody(String);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ApiError {
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
|
||||
struct ErrorContext {
|
||||
error_msg: String
|
||||
}
|
||||
|
||||
// Always use a limit to prevent DoS attacks.
|
||||
const LIMIT: u64 = 256;
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for RequestInfo {
|
||||
type Error = ApiError;
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||
let mut req_cookies = request.cookies();
|
||||
|
||||
for row in req_cookies.iter() {
|
||||
println!("{}: {}", row.name(), row.value());
|
||||
}
|
||||
// Initially set cookie
|
||||
if req_cookies.get(&"mirror-id").is_none() {
|
||||
|
||||
let new_uuid = Uuid::new_v4().to_string();
|
||||
|
||||
println!("Creating new cookie");
|
||||
|
||||
req_cookies.add(Cookie::new("mirror-id", new_uuid.clone()));
|
||||
|
||||
let address = if request.client_ip().is_some() {
|
||||
request.client_ip().unwrap().to_string()
|
||||
} else {
|
||||
"Unknown".to_string()
|
||||
};
|
||||
|
||||
let connection = &mut establish_connection();
|
||||
|
||||
create_client(connection, &address, &new_uuid);
|
||||
}
|
||||
|
||||
let mut header: Vec<Pair> = vec![];
|
||||
let mut cookies: Vec<Pair> = vec![];
|
||||
let mut query: Vec<Pair> = vec![];
|
||||
|
||||
// Compile header
|
||||
for row in request.headers().clone().into_iter() {
|
||||
let key: String = row.name().to_string();
|
||||
let value: String = row.value().to_string();
|
||||
|
||||
header.push(Pair{key:key, value:value});
|
||||
}
|
||||
|
||||
// Compile cookies
|
||||
for row in req_cookies.iter() {
|
||||
let key: String = row.name().to_string();
|
||||
let value: String = row.value().to_string();
|
||||
|
||||
cookies.push(Pair{key:key, value:value});
|
||||
}
|
||||
|
||||
// Compile query
|
||||
let request_query = request.raw_query_items();
|
||||
if request_query.is_some() {
|
||||
for row in request_query.unwrap() {
|
||||
let (key, value) = row.key_value_decoded();
|
||||
|
||||
query.push(Pair{key:key, value:value});
|
||||
}
|
||||
}
|
||||
|
||||
Outcome::Success(RequestInfo{
|
||||
header: header,
|
||||
cookies: cookies,
|
||||
query: query
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDataSimple for RequestBody {
|
||||
type Error = String;
|
||||
|
||||
fn from_data(_req: &Request, data: Data) -> data::Outcome<Self, String> {
|
||||
// Read the data into a String.
|
||||
let mut string = String::new();
|
||||
if let Err(e) = data.open().take(LIMIT).read_to_string(&mut string) {
|
||||
return Outcome::Failure((Status::InternalServerError, format!("{:?}", e)));
|
||||
}
|
||||
|
||||
// Return successfully.
|
||||
Outcome::Success(RequestBody(string))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index(_info: RequestInfo) -> Template {
|
||||
|
||||
//Redirect::to("/test")
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context{
|
||||
}
|
||||
|
||||
Template::render("index", Context {})
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
fn test_get(request: RequestInfo, cookies: Cookies) -> Template {
|
||||
|
||||
println!("{request:?}");
|
||||
|
||||
let cookie_id = cookies.get("mirror-id");
|
||||
|
||||
if cookie_id.is_some() {
|
||||
let connection = &mut establish_connection();
|
||||
let history_id = create_history_record(connection, cookie_id.unwrap().value(), "Get");
|
||||
|
||||
println!("Creating header Records for {history_id}");
|
||||
|
||||
for row in &request.header {
|
||||
create_pair_record(connection, history_id, PairType::Header as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
println!("Creating cookie Records for {history_id}");
|
||||
|
||||
for row in &request.cookies {
|
||||
create_pair_record(connection, history_id, PairType::Cookie as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
println!("Creating query Records for {history_id}");
|
||||
|
||||
for row in &request.query {
|
||||
create_pair_record(connection, history_id, PairType::Query as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context<'a> {
|
||||
request_type: &'a str,
|
||||
header: Vec<Pair>,
|
||||
cookies: Vec<Pair>,
|
||||
query: Vec<Pair>,
|
||||
}
|
||||
|
||||
let context = Context {
|
||||
request_type: "Get",
|
||||
header: request.header,
|
||||
cookies: request.cookies,
|
||||
query: request.query
|
||||
};
|
||||
|
||||
Template::render("request_details", context)
|
||||
}
|
||||
|
||||
#[post("/test", data = "<body>")]
|
||||
fn test_post(body: RequestBody, request: RequestInfo, cookies: Cookies) -> Template {
|
||||
|
||||
println!("{request:?}");
|
||||
|
||||
println!("Input: {}", body.0);
|
||||
|
||||
let cookie_id = cookies.get("mirror-id");
|
||||
|
||||
if cookie_id.is_some() {
|
||||
let connection = &mut establish_connection();
|
||||
let history_id = create_history_record(connection, cookie_id.unwrap().value(), "Post");
|
||||
|
||||
println!("Creating header Records for {history_id}");
|
||||
|
||||
for row in &request.header {
|
||||
create_pair_record(connection, history_id, PairType::Header as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
println!("Creating cookie Records for {history_id}");
|
||||
|
||||
for row in &request.cookies {
|
||||
create_pair_record(connection, history_id, PairType::Cookie as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
println!("Creating query Records for {history_id}");
|
||||
|
||||
for row in &request.query {
|
||||
create_pair_record(connection, history_id, PairType::Query as i32, &row.key, &row.value);
|
||||
}
|
||||
|
||||
println!("Creating body Records for {history_id}");
|
||||
create_pair_record(connection, history_id, PairType::Body as i32, "body", &body.0.clone());
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context<'a> {
|
||||
request_type: &'a str,
|
||||
header: Vec<Pair>,
|
||||
cookies: Vec<Pair>,
|
||||
query: Vec<Pair>,
|
||||
body: String
|
||||
}
|
||||
|
||||
let context = Context {
|
||||
request_type: "Post",
|
||||
header: request.header,
|
||||
cookies: request.cookies,
|
||||
query: request.query,
|
||||
body: body.0
|
||||
};
|
||||
|
||||
Template::render("request_details", context)
|
||||
}
|
||||
|
||||
#[get("/history")]
|
||||
fn history_req(cookies: Cookies) -> Template {
|
||||
|
||||
let cookie_id = cookies.get("mirror-id");
|
||||
|
||||
let cookie_id = cookie_id.unwrap().value();
|
||||
println!("Client ID: {}", cookie_id);
|
||||
|
||||
let connection = &mut establish_connection();
|
||||
let results = history::dsl::history
|
||||
.filter(history::client_id.eq(cookie_id.to_string()))
|
||||
.select(HistoryRecord::as_select())
|
||||
.load(connection)
|
||||
.expect("Error loading clients");
|
||||
|
||||
for record in results.iter() {
|
||||
println!("{:?}", record);
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct History {
|
||||
pub id: i32,
|
||||
pub client_id: String,
|
||||
pub request_type: String,
|
||||
pub timestamp: String
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context {
|
||||
history_records: Vec<History>
|
||||
}
|
||||
|
||||
let mut history_records: Vec<History> = Vec::new();
|
||||
|
||||
for history_rec in results {
|
||||
history_records.push(
|
||||
History {
|
||||
id: history_rec.id,
|
||||
client_id: history_rec.client_id,
|
||||
request_type: history_rec.request_type,
|
||||
timestamp: history_rec.timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Template::render("history", Context{
|
||||
history_records: history_records
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
#[get("/history/<history_id>")]
|
||||
fn history_details(history_id: i32, cookies: Cookies) -> Template {
|
||||
|
||||
let cookie_id = cookies.get("mirror-id");
|
||||
|
||||
let cookie_id = cookie_id.unwrap().value();
|
||||
println!("Client ID: {}", cookie_id);
|
||||
|
||||
let connection = &mut establish_connection();
|
||||
let results: Vec<HistoryRecord> = history::dsl::history
|
||||
.filter(history::id.eq(&history_id))
|
||||
.filter(history::client_id.eq(cookie_id.to_string()))
|
||||
.select(HistoryRecord::as_select())
|
||||
.load(connection)
|
||||
.expect("Error loading history records");
|
||||
|
||||
if results.len() <= 0 {
|
||||
|
||||
// Error
|
||||
return Template::render(
|
||||
"error",
|
||||
ErrorContext{ error_msg: "No Results Found. You may be unauthorized...".to_string() }
|
||||
);
|
||||
}
|
||||
|
||||
let connection = &mut establish_connection();
|
||||
|
||||
let pairs: Vec<PairRecord> = pair_records::dsl::pair_records
|
||||
.filter(pair_records::history_id.eq(history_id))
|
||||
.select(PairRecord::as_select())
|
||||
.load(connection)
|
||||
.expect("Error loading history records");
|
||||
|
||||
let body: String = match &pairs.iter().filter(|res| res.pair_type == PairType::Body as i32).last() {
|
||||
Some(pair) => pair.value.clone(),
|
||||
_ => "".to_string()
|
||||
};
|
||||
|
||||
let header: Vec<&PairRecord> = pairs.iter()
|
||||
.filter(|res: &&PairRecord| res.pair_type == PairType::Header as i32)
|
||||
.map(|res: &PairRecord| res)
|
||||
.collect();
|
||||
|
||||
let cookies: Vec<&PairRecord> = pairs.iter()
|
||||
.filter(|res: &&PairRecord| res.pair_type == PairType::Cookie as i32)
|
||||
.map(|res: &PairRecord| res)
|
||||
.collect();
|
||||
|
||||
let query: Vec<&PairRecord> = pairs.iter()
|
||||
.filter(|res: &&PairRecord| res.pair_type == PairType::Query as i32)
|
||||
.map(|res: &PairRecord| res)
|
||||
.collect();
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context<'a> {
|
||||
request_type: String,
|
||||
body: String,
|
||||
header: Vec<&'a PairRecord>,
|
||||
cookies: Vec<&'a PairRecord>,
|
||||
query: Vec<&'a PairRecord>,
|
||||
}
|
||||
|
||||
Template::render(
|
||||
"request_details",
|
||||
Context{
|
||||
request_type: results[0].request_type.clone(),
|
||||
body: body.to_string(),
|
||||
header: header,
|
||||
cookies: cookies,
|
||||
query: query
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[catch(404)]
|
||||
fn not_found(req: &Request) -> String {
|
||||
print!("{}", req);
|
||||
format!("Oh no! We couldn't find the requested path '{}'", req.uri())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
.register(catchers![not_found])
|
||||
.mount("/", routes![index, test_get, test_post, history_req, history_details])
|
||||
.attach(Template::fairing())
|
||||
.launch();
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
use serde::Serialize;
|
||||
use crate::schema::{
|
||||
clients,
|
||||
history,
|
||||
pair_records
|
||||
};
|
||||
use diesel::pg::Pg;
|
||||
|
||||
#[derive(Queryable, Selectable, Debug)]
|
||||
#[diesel(table_name = clients)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct Client {
|
||||
pub id: i32,
|
||||
pub ip: String,
|
||||
pub mirror_id: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = clients)]
|
||||
pub struct NewClient<'a> {
|
||||
pub ip: &'a str,
|
||||
pub mirror_id: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Debug)]
|
||||
#[diesel(table_name = history)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct HistoryRecord {
|
||||
pub id: i32,
|
||||
pub client_id: String,
|
||||
pub request_type: String,
|
||||
pub timestamp: NaiveDateTime
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = history)]
|
||||
pub struct NewHistoryRecord<'a> {
|
||||
pub client_id: &'a str,
|
||||
pub request_type: &'a str,
|
||||
pub timestamp: NaiveDateTime
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Debug, Serialize)]
|
||||
#[diesel(table_name = pair_records)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct PairRecord {
|
||||
pub id: i32,
|
||||
pub history_id: i32,
|
||||
pub pair_type: i32,
|
||||
pub key: String,
|
||||
pub value: String
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = pair_records)]
|
||||
pub struct NewPairRecord<'a> {
|
||||
pub history_id: i32,
|
||||
pub pair_type: i32,
|
||||
pub key: &'a str,
|
||||
pub value: &'a str
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
clients (id) {
|
||||
id -> Int4,
|
||||
ip -> Text,
|
||||
mirror_id -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
history (id) {
|
||||
id -> Int4,
|
||||
client_id -> Text,
|
||||
request_type -> Text,
|
||||
timestamp -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
ownership (id) {
|
||||
id -> Int4,
|
||||
owner_id -> Text,
|
||||
client_id -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
pair_records (id) {
|
||||
id -> Int4,
|
||||
history_id -> Int4,
|
||||
pair_type -> Int4,
|
||||
key -> Text,
|
||||
value -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
clients,
|
||||
history,
|
||||
ownership,
|
||||
pair_records,
|
||||
);
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column">
|
||||
<nav id="navbar" class="navbar navbar-expand-lg bg-body-tertiary mb-2">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Request Mirror</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/test">Test Page</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/history">History</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="body" class="p-5">
|
||||
<h1>Error Page</h1>
|
||||
<p>{{error_msg}}</p>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column">
|
||||
<nav id="navbar" class="navbar navbar-expand-lg bg-body-tertiary mb-5">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Request Mirror</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/test">Test Page</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/history">History</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="body" class="p-5">
|
||||
<div class="tab-pane fade show active" id="header" role="tabpanel" aria-labelledby="header-tab">
|
||||
<h1>Request History</h1>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>History ID</th>
|
||||
<th>Client ID</th>
|
||||
<th>Request Type</th>
|
||||
<th>TimeStamp</th>
|
||||
</tr>
|
||||
{{#each history_records}}
|
||||
<tr>
|
||||
<td><a href="/history/{{id}}">{{id}}</a></td>
|
||||
<td>{{client_id}}</td>
|
||||
<td>{{request_type}}</td>
|
||||
<td>{{timestamp}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
let records = [
|
||||
{{#each history_records}}
|
||||
{
|
||||
id: {{id}},
|
||||
client_id:"{{client_id}}",
|
||||
request_type: "{{request_type}}",
|
||||
timestamp:"{{timestamp}}"
|
||||
},
|
||||
{{/each}}
|
||||
];
|
||||
|
||||
function popupRecord(event, id) {
|
||||
console.log(records.filter(v=>v.id == id));
|
||||
}
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column">
|
||||
<nav id="navbar" class="navbar navbar-expand-lg bg-body-tertiary mb-5">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Request Mirror</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/test">Test Page</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/history">History</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="body" class="p-5">
|
||||
<h1>Test Form</h1>
|
||||
<div class="form-check form-switch">
|
||||
<input onchange="onRequestMethod(event)" class="form-check-input" type="checkbox" role="switch"
|
||||
id="requestMethodSwitch">
|
||||
<label class="form-check-label" for="requestMethodSwitch" id="requestMethodLabel">Post Request</label>
|
||||
</div>
|
||||
|
||||
<div id="function-header">
|
||||
<button class="btn btn-secondary" type="button" onclick="addField()">Add Field</button>
|
||||
<button class="btn btn-secondary" type="button" onclick="saveFields()">Save Form</button>
|
||||
<button class="btn btn-secondary" type="button" onclick="loadFields()">Load Form</button>
|
||||
<button class="btn btn-secondary" type="button" onclick="resetForm()">Reset Form</button>
|
||||
</div>
|
||||
<form id="submission-form" method="post" action="/test" onsubmit="saveForm()">
|
||||
<table class="m-2" style="width: 100rem;">
|
||||
<tbody id="field-groups">
|
||||
<tr id="table-header">
|
||||
<th>Value</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input class="btn btn-primary" type="submit" value="Submit"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
let fieldCount = 0;
|
||||
|
||||
let fieldInfo = {
|
||||
|
||||
}
|
||||
|
||||
function saveFields() {
|
||||
localStorage.setItem('mirror-save', JSON.stringify(fieldInfo));
|
||||
}
|
||||
|
||||
function loadFields() {
|
||||
let fieldstr = localStorage.getItem('mirror-save');
|
||||
|
||||
if (fieldstr) {
|
||||
|
||||
fieldInfo = JSON.parse(fieldstr);
|
||||
|
||||
resetForm();
|
||||
|
||||
for (let field in fieldInfo) {
|
||||
console.log(field);
|
||||
addField(fieldInfo[field]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
localStorage.removeItem('mirror-save');
|
||||
|
||||
fieldInfo = {};
|
||||
|
||||
for (let i = 0; i < fieldCount; i++) {
|
||||
document.getElementById(`row-${i}`).remove();;
|
||||
}
|
||||
|
||||
fieldCount = 0;
|
||||
}
|
||||
|
||||
function addField(field) {
|
||||
console.log(field);
|
||||
let newInput = document.createElement('tr');
|
||||
|
||||
newInput.id = `row-${fieldCount}`;
|
||||
|
||||
newInput.innerHTML = `
|
||||
<td>
|
||||
<input class="form-control" id='${fieldCount}-input' type='text' name='${fieldCount}' onchange="onValueChange('${fieldCount}', event)" value='${field?field.value:""}'/>
|
||||
</td>
|
||||
<td >
|
||||
<input class="form-control" id="${fieldCount}-name" type='text' onchange="onNameChange('${fieldCount}', event);" value='${field?field.name:fieldCount}'>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select col-auto" id="${fieldCount}-type" onchange='onInputTypeSelect("${fieldCount}", event)' aria-label="Field Type">
|
||||
<option value='text' selected>Text</option>
|
||||
<option value='email'>Email</option>
|
||||
<option value='password'>Password</option>
|
||||
<option value='number'>Number</option>
|
||||
<option value='range'>Range</option>
|
||||
<option value='url'>Url</option>
|
||||
<option value='date'>Date</option>
|
||||
<option value='datetime-local'>Date Time</option>
|
||||
<option value='color'>Color</option>
|
||||
<option value='checkbox'>Checkbox</option>
|
||||
<option value='radio'>Radio</option>
|
||||
<option value='file'>File</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class='btn btn-danger' onclick="delField(${fieldCount})">-</button>
|
||||
</td>`;
|
||||
|
||||
for (let element in document.getElementById(`${fieldCount}-type`)) {
|
||||
if(element.value === field.type) {
|
||||
element.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
fieldInfo[fieldCount.toString()] = {
|
||||
name: fieldCount.toString(),
|
||||
value: "",
|
||||
type: "text"
|
||||
}
|
||||
|
||||
document.getElementById("field-groups").appendChild(newInput);
|
||||
|
||||
|
||||
|
||||
fieldCount++;
|
||||
}
|
||||
|
||||
function delField(id) {
|
||||
delete fieldInfo[id];
|
||||
|
||||
document.getElementById("field-groups").removeChild(document.getElementById(id))
|
||||
}
|
||||
|
||||
function onValueChange(id, event) {
|
||||
fieldInfo[id].value = event.target.value;
|
||||
}
|
||||
|
||||
function onNameChange(id, event) {
|
||||
fieldInfo[id].name = event.target.value;
|
||||
document.getElementById(`${id}-input`).name = event.target.value;
|
||||
}
|
||||
function onInputTypeSelect(id, event) {
|
||||
fieldInfo[id].type = event.target.value;
|
||||
document.getElementById(`${id}-input`).type = event.target.value;
|
||||
}
|
||||
|
||||
function onRequestMethod(event) {
|
||||
document.getElementById('requestMethodLabel').innerText = event.target.checked ? 'Get Request' : 'Post Request';
|
||||
document.getElementById('submission-form').method = event.target.checked ? 'get' : 'post';
|
||||
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" defer>
|
||||
loadFields();
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column">
|
||||
<nav id="navbar" class="navbar navbar-expand-lg bg-body-tertiary mb-5">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Request Mirror</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/test">Test Page</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/history">History</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="body" class="d-flex flex-column p-4 container-xxl">
|
||||
<h1>{{request_type}} Request</h1>
|
||||
|
||||
{{#if body}}
|
||||
<div class="mb-2 mt-2 input-group">
|
||||
<span class="input-group-text">Request Body</span>
|
||||
<textarea class="form-control" aria-label="With textarea" readonly>{{body}}</textarea>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<!-- Tab Header-->
|
||||
<ul class="nav nav-tabs" id="RequestTabs" role="tablist">
|
||||
{{#if header}}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="header-tab" data-bs-toggle="tab" data-bs-target="#header" type="button"
|
||||
role="tab" aria-controls="header" aria-selected="true">Header</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if cookies}}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="cookies-tab" data-bs-toggle="tab" data-bs-target="#cookies" type="button"
|
||||
role="tab" aria-controls="cookies" aria-selected="false">Cookies</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if query}}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="query-tab" data-bs-toggle="tab" data-bs-target="#query" type="button" role="tab"
|
||||
aria-controls="query" aria-selected="false">Url Query Params</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mr-auto ml-auto" id="myTabContent">
|
||||
{{#if header}}
|
||||
<div class="tab-pane fade show active" id="header" role="tabpanel" aria-labelledby="header-tab">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
{{#each header}}
|
||||
<tr>
|
||||
<td>{{key}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if cookies}}
|
||||
<div class="tab-pane fade" id="cookies" role="tabpanel" aria-labelledby="cookies-tab">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
{{#each cookies}}
|
||||
<tr>
|
||||
<td>{{key}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if query}}
|
||||
<div class="tab-pane fade" id="query" role="tabpanel" aria-labelledby="query-tab">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
{{#each query}}
|
||||
<tr>
|
||||
<td>{{key}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in New Issue