Compare commits

...

23 Commits

Author SHA1 Message Date
Camerin Figueroa 2030d2ad1e
Merge pull request #15 from RaspberryProgramming/test
Updated Cargo packages and added configurable cookie expiration
2025-01-24 19:51:32 -05:00
Camerin Figueroa e3a2e9d632 Cargo Updates 2025-01-24 16:32:54 -05:00
Camerin Figueroa a408e753a8 Added Cookie expiration option/environment variable
The default expiration is 52 weeks.
2025-01-24 15:53:39 -05:00
Camerin Figueroa 0394851e97
Merge pull request #13 from RaspberryProgramming/test
Remove Self hosted runner
2024-12-04 15:46:02 -05:00
Camerin Figueroa 5eda5d0604 Remove Self hosted runner 2024-12-04 15:23:23 -05:00
Camerin Figueroa e646772844
Merge pull request #12 from RaspberryProgramming/test
Added information on deployment with an ansible playbook
2024-10-21 11:46:05 -04:00
RaspberryProgramming e358f8b3a7 Added information on deployment with an ansible playbook 2024-07-25 00:22:18 +00:00
Camerin Figueroa 4c9e2e49be
Merge pull request #11 from RaspberryProgramming/test
V0.1.3
2024-07-19 16:54:16 -04:00
Camerin Figueroa 05894df3e0
Merge pull request #10 from RaspberryProgramming/V0.1.3
Fixed bug where owner_id was hardcoded
2024-07-19 11:29:47 -04:00
RaspberryProgramming c26a0576d5 Fixed bug where owner_id was hardcoded 2024-07-19 15:29:06 +00:00
Camerin Figueroa 99eca430df
Merge pull request #9 from RaspberryProgramming/V0.1.3
V0.1.3
2024-07-19 11:21:48 -04:00
RaspberryProgramming 7743cb8eb6 Updated version and README 2024-07-19 15:14:22 +00:00
RaspberryProgramming f4826c5ca9 Ownership Registration - New functionality
added ability to copy client id to clipboard by clicking
Fixed submit bug where it would open a new page.
Added link to copy and allow remote clients to submit ownership registration. This can be done with things such as curl or other cli tools that can't visually see the html form on this page
2024-07-19 14:57:49 +00:00
RaspberryProgramming d5f13d63e1 Updated navbar highlight for each page 2024-07-19 14:21:16 +00:00
RaspberryProgramming 355066b197 Fixed cookie bug
When connecting for the first time, if a request uses the cookie jar the cookie may be pending.
Made a new function that will account for that and return the pending cookie if it exists. Otherwise it will return the cookie using the .get function of the cookiejar.
2024-07-19 14:20:02 +00:00
Camerin Figueroa 8eccd1ed24
Merge pull request #8 from RaspberryProgramming/test
Updates
2024-07-17 20:43:42 -04:00
Camerin Figueroa c81d7df97f
Update docker-test.yml 2024-07-17 20:05:03 -04:00
Camerin Figueroa 8e0e516a7d
Update docker-test.yml
removed on trigger for test pr's
2024-07-17 20:02:52 -04:00
RaspberryProgramming 0fef9757bb Added page titles 2024-07-17 23:55:12 +00:00
RaspberryProgramming 6b336c5a79 Revert "Update index.html.hbs"
This reverts commit e3de725725.
2024-07-17 21:05:54 +00:00
Camerin Figueroa c97de1de95
Merge pull request #7 from RaspberryProgramming/test-patch
Update index.html.hbs
2024-07-17 16:44:54 -04:00
Camerin Figueroa e3de725725
Update index.html.hbs 2024-07-17 16:41:09 -04:00
RaspberryProgramming 66426dd94b Created action to publish test image, added test branch to rust.yml and removed upload build artifact from rust.yml 2024-07-17 19:15:01 +00:00
15 changed files with 700 additions and 359 deletions

View File

@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: self-hosted
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build . -t raspberrypi99/request-mirror

22
.github/workflows/docker-test.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Build and Publish Test Docker Image
on:
push:
branches: [ "test"]
pull_request:
branches: [ "test" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build . -t raspberrypi99/request-mirror
- name: Dockerize
if: success()
uses: manusa/actions-publish-docker@v1.1.2
with:
name: raspberrypi99/request-mirror
tag: test
username: ${{ secrets.docker_hub_username }}
password: ${{ secrets.docker_hub_password }}

View File

@ -2,18 +2,16 @@ name: Build and Test Rust
on:
push:
branches: [ "main" ]
branches: [ "main", "test"]
pull_request:
branches: [ "main" ]
branches: [ "main", "test" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: self-hosted
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
@ -26,10 +24,4 @@ jobs:
- name: Build
run: cargo build --release --verbose
- name: Run tests
run: cargo test --release --verbose
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-build
path: target/release/request-mirror
run: cargo test --release --verbose

583
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,27 @@
[package]
name = "request-mirror"
version = "0.1.2"
version = "0.1.3"
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"] }
chrono = "0.4.39"
diesel = { version = "2.2.6", features = ["postgres", "chrono"] }
dotenvy = "0.15.7"
regex = "1.10.5"
regex = "1.11.1"
rocket = { version = "0.5.1", features = ["tls"] }
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["full"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.137"
tokio = { version = "1.43.0", features = ["full"] }
time = "0.3.37"
[dependencies.rocket_dyn_templates]
version = "0.2.0"
features = ["handlebars"]
[dependencies.uuid]
version = "1.7.0"
version = "1.12.1"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG

View File

@ -1,14 +1,14 @@
FROM rustlang/rust:nightly as builder
FROM rustlang/rust:nightly AS builder
WORKDIR /app
COPY src ./src
COPY Cargo.toml .
COPY Cargo.lock .
RUN cargo install --path .
FROM rustlang/rust:nightly as runner
FROM rustlang/rust:nightly AS runner
COPY migrations /migrations
RUN cargo install diesel_cli --no-default-features --features postgres
RUN apt update && apt install -y libpq-dev libc6
RUN apt update && apt upgrade -y && apt install -y libpq-dev libc6
COPY --from=builder /usr/local/cargo/bin/request-mirror /usr/local/bin/request-mirror
COPY ./templates /templates
COPY .env.docker /.env

107
README.md
View File

@ -6,9 +6,8 @@ This application provides a web ui for sending get/post requests and provides a
![Build and Test Rust](https://github.com/RaspberryProgramming/request-mirror/actions/workflows/rust.yml/badge.svg)
## TODO:
- Update Readme
- Document
- Develop Pipelines
N/a
## Docker
@ -51,6 +50,104 @@ Then push the image
docker push raspberrypi99/request-mirror:latest
```
## Deploying to azure
## Development Environment
TODO
During development, you'll want to use a few tools to help work on this project.
First, you'll want to install docker.
For environments with a GUI: https://docs.docker.com/desktop/
For environments without a GUI: https://docs.docker.com/engine/install/
You can use docker for many things. This project has a set of files for creating and deploying as a docker container. In order to start development, you'll need a postgres container. By default in the .env file, the rust code will attempt to connect a localhost instance of postgres.
Use the following command to start a postgres container.
```bash
docker run --name postgres -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=password -e POSTGRES_USER=postgres -e POSTGRES_DB=request_mirror_db -d postgres
```
This will start up an instance of postgres to only localhost. Remote computers can't connect to the database. This setup does not use any volumes, meaning that when the container is removed, the data will be gone. If you'd like to add volumes, you can run the following command instead which maps a new postgres-data volume to /var/lib/postgresql/data.
```bash
docker run --name postgres -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=password -e POSTGRES_USER=postgres -e POSTGRES_DB=request_mirror_db -v postgres-data:/var/lib/postgresql/data -d postgres:latest
```
Now that postgres is running, you can now install rust if you haven't already. Follow the instructions on the following site [rustup.rs](https://rustup.rs/)
Follow the instructions for installing the stable toolchain for rust. You may need to log in and log out after installation.
**Notice**: Older versions of request mirror used rocket v0.4.x which required the nightly toolchain, please update your repo/fork.
Next, we can install [diesel](https://diesel.rs/), an ORM and query builder for rust. This is how we deploy tables to our database. Install diesel cli:
```bash
cargo install diesel_cli --no-default-features --features postgres
```
You can deploy the database with the following command:
**Notice**: If you are not adding a volume to your postgres database, you may need to re-run this step each time you create the postgres docker container.
```bash
diesel migration run
```
Now that the database is ready and rust is installed, we can move onto running the project.
```bash
cargo run
```
You can also run the following to run a release binary
```bash
cargo run --release
```
### Build Docker Image and Run Locally
You can build the docker image by running the following comand
```bash
docker build . -t raspberrypi99/request-mirror
```
Next, you can run the project using the following command. This can be run even with the development postgres container running. This will open port 80 for the user to connect to.
```bash
docker compose up -d
```
## Ansible Playbook
You can get access to an ansible playbook and associated files by checking out the following repository
[https://github.com/RaspberryProgramming/CamsAnsibleLibrary/](https://github.com/RaspberryProgramming/CamsAnsibleLibrary)
You can clone the repository
```bash
git clone https://github.com/RaspberryProgramming/CamsAnsibleLibrary
cd CamsAnsibleLibrary/Request\ Mirror/
```
Next, you can create your "inventory" file
```
[request-mirror-host]
10.1.2.3
```
If you haven't already, setup an ssh key so you can automatically log into your remote host as root. If you decide to use a user with sudo privileges you may need to modify the playbook on your own.
```bash
ansible-playbook request_mirror_deployment.yml -i inventory
```
If you need to enter a root password, you can add the -K argument
```bash
ansible-playbook request_mirror_deployment.yml -i inventory -K
```

View File

@ -17,6 +17,8 @@ services:
- 80:80
environment:
- DATABASE_URL=postgres://postgres:Password123@postgres/request_mirror_db
# Set COOKIE_EXPIRATION in weeks
# - COOKIE_EXPIRATION=52
depends_on:
postgres:
condition: service_healthy

View File

@ -3,6 +3,7 @@ pub mod schema;
use chrono::offset::Utc;
use diesel::prelude::*;
use rocket::http::CookieJar;
use std::env;
use dotenvy::dotenv;
use models::{
@ -46,6 +47,38 @@ pub fn establish_connection() -> PgConnection {
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}
/// returns default cookie expiration in weeks
///
/// This value can be set by modifying an environment variable or setting in the .env file.
///
/// Example:
///
/// ```rust,ignore
/// use request_mirror::establish_connection;
///
/// let connection = establish_connection();
/// ```
pub fn cookie_expiration() -> i64 {
let key: &str = "COOKIE_EXPIRATION";
let expiration: i64;
match env::var(key) {
Ok(val) => {
match val.parse::<i64>() {
Ok(v) => expiration = v,
Err(e) => expiration = 52
}
},
Err(_e) => {
dotenv().ok();
expiration = 52;
}
}
expiration
}
/// Used to create a new client in the database. You need to pass a connection, the ip and client_id
///
/// Example:
@ -129,3 +162,12 @@ pub fn ownership_exists(client_id: &str, owner_id: &str, connection: &mut PgConn
ownerships.len() > 0
}
/// Looks for a cookie in the cookiejar. If it is a pending cookie, the pending will be returned as a String.
/// This accounts for if this client hasn't connected before and is making its first request
pub fn get_cookie(key: &str, cookies: &CookieJar<'_>) -> String {
match cookies.get_pending(key) {
Some(v) => v.value().to_string(),
None => cookies.get(key).unwrap().value().to_string()
}
}

View File

@ -9,6 +9,7 @@ use rocket::outcome::Outcome;
use rocket_dyn_templates::Template;
use serde::Serialize;
use rocket::http::{Cookie, CookieJar, Status};
use time::{Duration, OffsetDateTime};
use uuid::Uuid;
use request_mirror::models::*;
use diesel::prelude::*;
@ -61,7 +62,14 @@ impl<'r> FromRequest<'r> for RequestInfo {
let new_uuid = Uuid::new_v4().to_string();
println!("Creating new cookie");
req_cookies.add(Cookie::new("mirror-id", new_uuid.clone()));
let mut cookie = Cookie::new("mirror-id", new_uuid.clone());
let mut now = OffsetDateTime::now_utc();
now += Duration::weeks(cookie_expiration()); // Default expiration is 52 weeks
cookie.set_expires(now);
req_cookies.add(cookie);
let address = if req.client_ip().is_some() {
req.client_ip().unwrap().to_string()
@ -152,30 +160,27 @@ fn index(_info: RequestInfo) -> Template {
#[get("/test")]
fn test_get(request: RequestInfo, cookies: &CookieJar<'_>) -> Template {
let client_id = cookies.get("mirror-id");
let client_id = get_cookie("mirror-id", cookies);
// If the cookie exists, create new database records for this request
if client_id.is_some() {
let connection = &mut establish_connection();
// create new database records for this request
let connection = &mut establish_connection();
// Create new history record and retrieve new history_id
let history_id = create_history_record(connection, client_id.unwrap().value(), "Get");
// Create new history record and retrieve new history_id
let history_id = create_history_record(connection, &client_id, "Get");
// Create header pair records
for row in &request.header {
create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
}
// Create cookie pair records
for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
}
// Create query pair records
for row in &request.query {
create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
}
// Create header pair records
for row in &request.header {
create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
}
// Create cookie pair records
for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
}
// Create query pair records
for row in &request.query {
create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
}
// Define context for the template
@ -204,36 +209,33 @@ fn test_get(request: RequestInfo, cookies: &CookieJar<'_>) -> Template {
#[post("/test", data = "<body>")]
fn test_post(body: RequestBody, request: RequestInfo, cookies: &CookieJar<'_>) -> Template {
let client_id = cookies.get("mirror-id");
let client_id = get_cookie("mirror-id", cookies);
// If the cookie exists, create new database records for this request
if client_id.is_some() {
let connection = &mut establish_connection();
// Create new history record and retrieve new history_id
let history_id = create_history_record(connection, client_id.unwrap().value(), "Post");
// Create header pair records
for row in &request.header {
create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
}
// Create cookie pair records
for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
}
// Create query pair records
for row in &request.query {
create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
}
// Create a pair record for body of the request
println!("Creating body Records for {history_id}");
create_pair_record(connection, history_id, PairType::Body, "body", &body.0.clone());
// Create new database records for this request
let connection = &mut establish_connection();
// Create new history record and retrieve new history_id
let history_id = create_history_record(connection, &client_id, "Post");
// Create header pair records
for row in &request.header {
create_pair_record(connection, history_id, PairType::Header, &row.key, &row.value);
}
// Create cookie pair records
for row in &request.cookies {
create_pair_record(connection, history_id, PairType::Cookie, &row.key, &row.value);
}
// Create query pair records
for row in &request.query {
create_pair_record(connection, history_id, PairType::Query, &row.key, &row.value);
}
// Create a pair record for body of the request
println!("Creating body Records for {history_id}");
create_pair_record(connection, history_id, PairType::Body, "body", &body.0.clone());
// Define context for the template
#[derive(Serialize)]
struct Context<'a> {
@ -260,10 +262,10 @@ fn test_post(body: RequestBody, request: RequestInfo, cookies: &CookieJar<'_>) -
/// Request function that returns a history of requests that the current client has made
/// The user can click a history_id and view the request itself
#[get("/history")]
fn history_req(cookies: &CookieJar<'_>) -> Template {
fn history_req(_info: RequestInfo, cookies: &CookieJar<'_>) -> Template {
// Get the client_id from the cookies
let client_id = cookies.get("mirror-id").unwrap().value();
let client_id = get_cookie("mirror-id", cookies);
let connection = &mut establish_connection();
@ -275,7 +277,7 @@ fn history_req(cookies: &CookieJar<'_>) -> Template {
.expect("Error loading clients");
// Get ownership relationships
let ownerships: Vec<Ownership> = get_ownerships(client_id, connection);
let ownerships: Vec<Ownership> = get_ownerships(&client_id, connection);
// Add any records that owned clients have
for ownership in ownerships {
@ -325,14 +327,14 @@ fn history_req(cookies: &CookieJar<'_>) -> Template {
/// request that was recorded to the database.
/// This includes the body of a post request, headers, cookies and query parameters.
#[get("/history/<history_id>")]
fn history_details(history_id: i64, cookies: &CookieJar<'_>) -> Template {
fn history_details(history_id: i64, _info: RequestInfo, cookies: &CookieJar<'_>) -> Template {
let client_id = cookies.get("mirror-id").unwrap().value();
let client_id = get_cookie("mirror-id", cookies);
let connection: &mut PgConnection = &mut establish_connection();
// Get owned client ids
let owned_clients: Vec<String> = get_ownerships(client_id, connection)
let owned_clients: Vec<String> = get_ownerships(&client_id, connection)
.iter()
.map(|x|x.client_id.to_string())
.collect::<Vec<String>>();
@ -415,26 +417,33 @@ fn history_details(history_id: i64, cookies: &CookieJar<'_>) -> Template {
#[get("/ownership_registration")]
fn ownership_registration(info: RequestInfo, cookies: &CookieJar<'_>) -> Template {
let client_id = cookies.get("mirror-id").unwrap().value().to_string();
let host_pair = info.header.iter().find(|&h| h.key == "host").unwrap();
let client_id = get_cookie("mirror-id", cookies);
let owner_id_pair = info.find_query_key("owner_id");
let mut disp_owner_reg = false;
let mut failed_owner_reg = false;
let re = Regex::new(r"^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$").unwrap();
let connection: &mut PgConnection = &mut establish_connection();
let ownerships: Vec<Ownership> = get_ownerships(&client_id, connection);
let owner_id = match owner_id_pair {
Some(v) => v.value.to_string(),
Some(v) => v.value.trim().to_string(),
None => "".to_string()
};
if
owner_id_pair.is_some() &&
owner_id_pair.is_some()
{
if
re.is_match(&owner_id) &&
owner_id != client_id &&
!ownership_exists(&client_id, &owner_id, connection)
{
create_owner_record(connection, owner_id.clone(), client_id.clone());
disp_owner_reg = true;
{
create_owner_record(connection, owner_id.clone(), client_id.clone());
disp_owner_reg = true;
} else {
failed_owner_reg = true;
}
}
#[derive(Serialize)]
@ -442,14 +451,18 @@ fn ownership_registration(info: RequestInfo, cookies: &CookieJar<'_>) -> Templat
client_id: String,
owner_id: String,
disp_owner_reg: bool,
ownerships: Vec<Ownership>
failed_owner_reg: bool,
ownerships: Vec<Ownership>,
host: String
}
Template::render("ownership_registration", Context {
client_id,
owner_id,
disp_owner_reg,
ownerships
failed_owner_reg,
ownerships,
host: host_pair.value.to_string()
})
}

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>Request Mirror - Error</title>
</head>
<body class="d-flex flex-column">
@ -18,7 +19,7 @@
<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>
<a class="nav-link" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/test">Test Page</a>

View File

@ -3,9 +3,9 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>Request Mirror - History</title>
</head>
<body class="d-flex flex-column">
@ -19,13 +19,13 @@
<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>
<a class="nav-link" 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>
<a class="nav-link active" href="/history">History</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/ownership_registration">Ownership Registration</a>

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>Request Mirror - Home</title>
</head>
<body>

View File

@ -3,9 +3,9 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>Request Mirror - Ownership Registration</title>
</head>
<body class="d-flex flex-column">
@ -19,7 +19,7 @@
<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>
<a class="nav-link" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/test">Test Page</a>
@ -28,7 +28,7 @@
<a class="nav-link" href="/history">History</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/ownership_registration">Ownership Registration</a>
<a class="nav-link active" href="/ownership_registration">Ownership Registration</a>
</li>
</ul>
</div>
@ -36,19 +36,27 @@
</nav>
<div id="body" class="p-5">
<h3>Your Client ID: {{client_id}}</h3>
<p>
<h5>
Your Client ID:
</h5>
<button id="client_id" class="border-0 bg-transparent" onclick="copy_content(event)" data-bs-toggle="tooltip"
data-bs-title="Click To Copy">
{{client_id}}
</button>
</p>
<p>
<h5>
Registration Link:
</h5>
<button id="reg_link" class="border-0 bg-transparent" onclick="copy_content(event)" data-bs-toggle="tooltip"
data-bs-title="Click To Copy">
{{host}}/ownership_registration?owner_id={{client_id}}
</button>
</p>
<form target="/owner_registration">
<h5>Ownership Registration</h5>
<input type="text" name="owner_id">
</form>
{{#if disp_owner_reg}}
<p>Registered new owner {{owner_id}} for {{client_id}}</p>
{{/if}}
<h3>Owned Clients</h3>
<table>
<h5>Owned Clients</h5>
<table class="table table-striped m-1">
<tr>
<th>Client ID</th>
</tr>
@ -60,10 +68,82 @@
</tr>
{{/each}}
</table>
<form action="/ownership_registration" method="get">
<h5>Ownership Registration</h5>
<div class="p-1">
<h6>Instructions:</h6>
<p>
Copy the Client ID from the browser you'd like to see your requests. Paste it into the Owner ID Form and
submit.
</p>
<p>
This will allow the other client to access any requests you've made in the history page.
</p>
<p>
If you'd like to register a new owner on a client that doesn't have access to this html form, you can make the
request directly
by making an http/https request to the registration link above. Click it to copy to your clipboard.
</p>
</div>
<div class="mb-3 ms-1 me-1">
<label for="owner_id" class="form-label">Owner ID</label>
<input class="form-control" type="text" name="owner_id">
</div>
<div class="">
<button type="submit" class="btn btn-primary mb-3">Submit</button>
</div>
</form>
{{#if disp_owner_reg}}
<p>Registered new owner {{owner_id}} for {{client_id}}</p>
{{/if}}
{{#if failed_owner_reg}}
<p>Failed to register {{owner_id}} for {{client_id}}.</p>
<p>This may have ocurred because the owner_id does not exist, is already registered as an owner, or that it is the same as the client_id.</p>
{{/if}}
</div>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="clipboard_toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<svg class="bd-placeholder-img rounded me-2" width="20" height="20" xmlns="http://www.w3.org/2000/svg"
aria-hidden="true" preserveAspectRatio="xMidYMid slice" focusable="false">
<rect width="100%" height="100%" fill="#007aff"></rect>
</svg>
<strong class="me-auto">Clipboard</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
Copied to Clipboard.
</div>
</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>
<script>
function copy_content(event) {
// Get the text field
var copyText = document.getElementById(event.target.id);
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.innerText);
const toastLiveExample = document.getElementById('clipboard_toast')
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastBootstrap.show()
}
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
</script>
</body>
</html>

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>Request Mirror - Request Details</title>
</head>
<body class="d-flex flex-column">
@ -18,10 +19,10 @@
<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>
<a class="nav-link" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/test">Test Page</a>
<a class="nav-link active" href="/test">Test Page</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/history">History</a>