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