added functionality

This commit is contained in:
Arlo Filley 2023-02-10 11:02:15 +00:00
parent 6dc7d6a033
commit 860bd085e3
15 changed files with 375 additions and 283 deletions

6
.gitignore vendored
View File

@ -5,3 +5,9 @@ Cargo.lock
.DS_Store
.gitignore
/.database
/websites/Servers
/websites/BitBurner
start.sh
TODO.md
/server
/servers_old

View File

@ -6,7 +6,8 @@ edition = "2021"
[dependencies]
rusqlite = "0.28.0"
rand = "0.8.5"
sha256 = "1.1.1"
[dependencies.rocket]
version = "0.5.0-rc.2"
features = ["json"]
features = ["json", "tls"]

View File

@ -5,11 +5,15 @@ workers = 4
max_blocking = 512
keep_alive = 5
ident = "Rocket"
log_level = "critical"
log_level = "normal"
temp_dir = "/tmp"
cli_colors = true
## NOTE: Don't (!) use this key! Generate your own!
secret_key = "abcdefghijklmnop"
secret_key = "n@Rz%H4ORaf2u*H@yWtoSwRC0qf%t&yjRhmb30A7Y4IEWGMl50"
[global.tls]
certs = "/etc/letsencrypt/live/arlofilley.com/fullchain.pem"
key = "/etc/letsencrypt/live/arlofilley.com/privkey.pem"
[default.limits]
form = "64 kB"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,35 +6,48 @@
//! - move structures into a different file
//! - find a way to make logging in more secure (password hashes?)
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Header;
use rocket::{Request, Response};
pub struct CORS;
#[rocket::async_trait]
impl Fairing for CORS {
fn info(&self) -> Info {
Info {
name: "Add CORS headers to responses",
kind: Kind::Response,
}
}
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
response.set_header(Header::new(
"Access-Control-Allow-Methods",
"POST, GET, PATCH, OPTIONS",
));
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
}
}
// Imports for rocket
#[macro_use] extern crate rocket;
#[macro_use]
extern crate rocket;
use rocket::{
Rocket,
Build,
fs::{
FileServer,
relative
},
fs::{relative, FileServer},
Build, Rocket,
};
mod typing;
mod servers;
mod typing;
use crate::typing::sql::*;
use crate::typing::user::{
get_tests,
login,
sign_up
};
use crate::typing::test::{
create_test,
new_test
};
use crate::servers::server::{server, server_info};
use crate::typing::leaderboard::leaderboard;
use crate::servers::server::{
server,
server_info
};
use crate::typing::sql::*;
use crate::typing::test::{create_test, new_test};
use crate::typing::user::{get_tests, login, sign_up};
// Imports for sql, see sql.rs for more information
@ -45,28 +58,34 @@ fn test() -> String {
String::from("Hello World!")
}
/// The main function which builds and launches the
/// webserver with all appropriate routes and fileservers
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build()
// testing only, should return "Hello world"
.mount("/test", routes![test])
// hosts the api routes necessary for the website
// to interact with the database
.mount("/api", routes![
create_database, sign_up,
create_test, login, get_tests,
leaderboard, new_test,
])
.mount("/api", routes![
server, server_info
])
// hosts the fileserver
.mount("/typing", FileServer::from(relative!("websites/Typing")))
.mount("/servers", FileServer::from(relative!("websites/Servers")))
.attach(CORS)
// testing only, should return "Hello world"
.mount("/test", routes![test])
// hosts the api routes necessary for the website
// to interact with the database
.mount(
"/api",
routes![
create_database,
sign_up,
create_test,
login,
get_tests,
leaderboard,
new_test,
],
)
.mount("/api", routes![server, server_info])
// hosts the fileserver
.mount("/typing", FileServer::from(relative!("websites/Typing")))
.mount("/servers", FileServer::from(relative!("websites/Servers")))
.mount(
"/BitBurner",
FileServer::from(relative!("websites/BitBurner")),
)
}

View File

@ -1,21 +1,11 @@
use rocket::serde::{
json::{
Json,
to_string, from_str
},
Deserialize,
Serialize
json::{from_str, to_string, Json},
Deserialize, Serialize,
};
use std::{
io::{
Write,
Read
},
fs::{
File,
read_dir,
},
fs::{read_dir, File},
io::{Read, Write},
};
#[derive(Deserialize, Serialize, Clone, Debug)]
@ -26,7 +16,7 @@ struct Process {
run_time: String,
id: String,
user_id: String,
virtual_memory: String
virtual_memory: String,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
@ -37,7 +27,15 @@ struct Disk {
total_space: String,
available_space: String,
usage: String,
file_system: String
file_system: String,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(crate = "rocket::serde")]
struct CPU {
core_count: String,
cache: String,
clock_speed: String,
}
#[derive(Deserialize, Serialize)]
@ -57,11 +55,12 @@ pub struct System {
swap_usage: String,
disks: Vec<Disk>,
processes: Vec<Process>,
cpu: CPU,
}
#[post("/servers", data = "<data>")]
pub fn server(data: Json<System>) {
if data.key == "0etnmXPSr95@FNy6A3U9Bw*ZupNIR85zI!hRFGIdj6SW$Tu0q%" {
if data.key == "0etnmXPSr95@FNy6A3U9Bw*ZupNIR85zI!hRFGIdj6SW$T" {
println!("Data From : {}", data.host_name);
}
@ -88,6 +87,7 @@ pub fn server(data: Json<System>) {
swap_usage: format!("{}", data.swap_usage),
disks: data.disks.clone(),
processes: data.processes.clone(),
cpu: data.cpu.clone(),
};
let string = match to_string(&data) {
@ -98,10 +98,7 @@ pub fn server(data: Json<System>) {
Ok(string) => string,
};
let write = file.write_all(
string
.as_bytes()
);
let write = file.write_all(string.as_bytes());
match write {
Err(why) => println!("Error {why}"),
@ -116,7 +113,7 @@ pub fn server_info() -> Json<Vec<System>> {
Err(why) => {
println!("Error: {why}");
read_dir("./server/").unwrap()
},
}
Ok(dir) => dir,
};
let mut file: File;
@ -128,9 +125,10 @@ pub fn server_info() -> Json<Vec<System>> {
Err(ref why) => {
println!("Error: {why}");
path.unwrap()
},
Ok(path) => path
}.path();
}
Ok(path) => path,
}
.path();
file = match File::open(format!("{}", path.display())) {
Err(why) => {

View File

@ -26,7 +26,7 @@ pub struct User<'r> {
pub fn sign_up(user: Json<User<'_>>) {
create_user(
user.username,
user.password
&sha256::digest(user.password),
).expect("Error: Couldn't create new user");
}
@ -44,7 +44,7 @@ pub fn get_tests(user_id: u32) -> Json<Vec<Test>> {
/// Accessible from http://url/api/login
#[get("/login/<username>/<password>")]
pub fn login(username: &str, password: &str) -> String {
let user_id = find_user(username, password).expect("error finding user_id");
let user_id = find_user(username, &sha256::digest(password)).expect("error finding user_id");
user_id.to_string()
}

View File

@ -1,19 +1,31 @@
:root {
--navbar: #52b2cf;
--navbar-button: #283F3B;
--background: #7EC4CF;
--navbar: #333333;
/* Navbar Button Properties */
--navbar-button: #058ED9;
--navbar-button-hover: #283F3B;
--navbar-button-border: #058ED9;
/* */
--background: #1f1f1f;
/* Table Properties*/
--table: #FFD07B;
--table-top:#FDB833;
--title: #296EB4;
/* Title Properties */
--title-background: #333333;
--title-text: #ffffff;
/* General Properties */
--text: #000000;
--border: #000000;
}
body {
background: var(--background);
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: medium;
margin: 0px;
}
div {
@ -25,59 +37,118 @@ div {
}
h1 {
color:black;
color: var(--title-text);
text-align: center;
width: 100%;
margin: 0px;
}
#all, #systems {
display: inline;
}
#navbar {
width: 100%;
padding: 20px 0 0 10px;
height: 96%;
width: 15%;
position: fixed;
display: block;
background: var(--navbar);
border: 1px black solid;
padding: 5px;
border-radius: 10px;
}
#navbar h2 {
color: white;
}
#systems {
display: flex;
flex-direction: column;
width: 95%;
}
#navbar #option-buttons {
display: flex;
flex-direction: column;
width: 95%;
}
#navbar #option-buttons button {
background-color: #32DE8A;
border-color: #32DE8A;
}
#navbar button {
transition-duration: 0.2s;
border-radius: 8px;
background-color: var(--navbar-button);
color: wheat;
border: 3px black solid;
padding: 15px;
margin-bottom: 8px;
color: var(--text);
border: none;
padding: 10px;
font-size: larger;
}
#navbar button:focus {
background-color: #23296c;
#navbar button:hover, #navbar #option-buttons button:hover {
box-shadow:
0 12px 16px 0 rgba(0,0,0,0.24),
0 17px 50px 0 rgba(0,0,0,0.19);
transition-duration: 0.5s;
}
#navbar #allSystemsButton {
background-color: #4d23a8;
width: 97%;
padding: 36px;
color: #FFE3E3;
font-size: 35px;
}
#navbar #allSystemsButton:hover, #navbar #allSystemsButton:focus {
background-color: #3E1C87;
}
#navbar #option-buttons button:hover, #navbar #option-buttons button:focus {
background-color: #1DAF68;
}
#navbar button:hover {
background-color: #fc9e4f;
color: var(--text);
border: 3px var(--border) solid;
background-color: #04699F;
}
#SystemDiv {
background: var(--title);
#SystemName {
background: var(--title-background);
width: 100%;
font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-family:
system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 50px;
padding-bottom: 20px;
border-bottom: black 1px solid;
width: 84%;
margin-left: 16%;
border-radius: 16px;
}
#system {
margin: 0px;
#tables {
display: block;
width: 84%;
margin-left: 15%;
padding: 10px 0 0 20px;
border: none;
}
table {
border: 2px black solid;
margin: 0px;
width: 100%;
border-collapse: collapse;
margin: 0px;
padding: 0px;
border-spacing: 0px;
border: 2px var(--border) solid;
border-radius: 20px;
}
table tr:nth-child(n) {
@ -89,11 +160,10 @@ table tr:nth-child(1) {
}
tr {
border: 1px black solid;
border: 1px black dotted;
}
td {
border: 1px black solid;
text-align: center;
padding: 10px;
font-size: larger;

View File

@ -7,25 +7,40 @@
<title id="title">Leaderboard</title>
<link rel="stylesheet" href="index.css">
<script src="index.js" defer="true"></script>
<script src="cpu.js" defer="true"></script>
</head>
<body>
<div id="SystemDiv"><h1 id="system">Typing Leaderboard</h1></div>
<div id="navbar">
<div id="allButton">
<button id="all">all</button>
<button id="allSystemsButton">All Systems</button>
</div>
<div id="systems">
<button>Arch</button>
<h2>Options</h2>
<div id="option-buttons">
<button id="systeminfo">System</button>
<button id="Disks Header">Disks</button>
<button id="Processes Header">Processes</button>
<button id="CPU Header">CPU</button>
</div>
</div>
<div id="navbar">
<button id="systeminfo">System</button>
<button id="Disks Header">Disks</button>
<button id="Processes Header">Processes</button>
<h2>Systems</h2>
<div id="systems"></div>
</div>
<div id="systemInfo" align="center"></div>
<div id="disks" align="center"></div>
<div id="processes" align="center"></div>
<div id="SystemName">
<h1 id="system">
Loading...
</h1>
</div>
<div id="tables">
<div id="allSystemInfo" align="center"></div>
<div id="systemInfo" align="center"></div>
<div id="disks" align="center"></div>
<div id="processes" align="center"></div>
<div id="CPU" align="center"></div>
</div>
</body>
</html>

View File

@ -1,34 +1,94 @@
get();
let disks = false;
let processes = false;
let sysinfo = true;
let allSysInfo = false;
let cpu = false;
let json;
let server = 0;
setInterval(get, 5000);
function get() {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://arlofilley.com/api/server_info');
xhr.open('GET', 'https://arlofilley.com/api/server_info');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.send();
xhr.onload = () => {
json = xhr.response
createElements(json[server]);
createElements(json);
createButtons(json);
};
};
function reset_tables() {
disks = false;
processes = false;
sysinfo = false;
allSysInfo = false;
cpu = false;
}
function createElements(pJson) {
let title = document.getElementById(`system`);
title.textContent = pJson.host_name;
document.title = pJson.host_name;
title.textContent = pJson[server].host_name;
document.title = pJson[server].host_name;
createProcesses(pJson);
createDisks(pJson);
createSystemInfo(pJson);
createAllSystemInfo(pJson);
createProcesses(pJson[server]);
createDisks(pJson[server]);
createSystemInfo(pJson[server]);
create_cpu_table(pJson[server], cpu);
};
function createAllSystemInfo(systems) {
let div = document.getElementById(`allSystemInfo`)
while (div.firstChild) {
div.removeChild(div.firstChild)
}
if (!allSysInfo) return;
document.title = "All Systems"
let title = document.getElementById('system');
title.textContent = "All Systems"
let table = document.createElement(`table`);
let tableBody = document.createElement(`tbody`);
table.appendChild(tableBody);
let tr = document.createElement(`tr`);
tableBody.appendChild(tr)
create_table_element(
tr,
"",
["Name", "OS", "Uptime", "Total Ram", "Used Ram",
"Available Ram", "Ram Usage", "Total Swap", "Used Swap",
"Available Swap", "Swap Usage"]
);
tableBody.appendChild(tr);
tr = document.createElement(`tr`);
for (let i = 0; i < systems.length; i++) {
let system = systems[i];
create_table_element(
tr,
"",
[`${system.host_name}`, `${system.os}`, `${system.uptime}`, `${system.total_ram}`,
`${system.used_ram}`, `${system.available_ram}`, `${system.ram_usage}%`, `${system.total_swap}`,
`${system.used_swap}`, `${system.available_swap}`, `${system.swap_usage}%`]
)
tableBody.appendChild(tr);
tr = document.createElement(`tr`);
}
div.appendChild(table);
}
function createSystemInfo(pJson) {
let div = document.getElementById(`systemInfo`);
@ -45,65 +105,23 @@ function createSystemInfo(pJson) {
let tr = document.createElement('tr');
tableBody.appendChild(tr);
let td = document.createElement('td');
td.appendChild(document.createTextNode(`Name`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`OS`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Uptime`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Total Ram`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Used Ram`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Available Ram`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Total Swap`));
tr.appendChild(td);
tableBody.appendChild(tr);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Used Swap`));
tr.appendChild(td);
tableBody.appendChild(tr);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Available Swap`));
tr.appendChild(td);
create_table_element(
tr,
"",
["Name", "OS", "Uptime", "Total Ram", "Used Ram",
"Available Ram", "Ram Usage", "Total Swap", "Used Swap",
"Available Swap", "Swap Usage"]
);
tableBody.appendChild(tr);
tr = document.createElement('tr');
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.host_name));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.os));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.uptime));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.total_ram));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.used_ram));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.available_ram));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.total_swap));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.used_swap));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(pJson.available_swap));
tr.appendChild(td);
create_table_element(
tr,
"",
[`${pJson.host_name}`, `${pJson.os}`, `${pJson.uptime}`, `${pJson.total_ram}`,
`${pJson.used_ram}`, `${pJson.available_ram}`, `${pJson.ram_usage}%`, `${pJson.total_swap}`,
`${pJson.used_swap}`, `${pJson.available_swap}`, `${pJson.swap_usage}%`]
);
tableBody.appendChild(tr);
tr = document.createElement('tr');
@ -126,51 +144,24 @@ function createProcesses(pJson) {
let tr = document.createElement('tr');
tableBody.appendChild(tr);
let td = document.createElement('td');
td.appendChild(document.createTextNode(`Name`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Memory`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Run Time`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Process ID`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`User ID`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Virtual Memory`));
tr.appendChild(td);
create_table_element(
tr,
"",
["Name", "Memory", "Run Time", "Process ID",
"User ID", "Virtual Memory"]
);
tableBody.appendChild(tr);
tr = document.createElement('tr');
for (let i = 0; i < pJson.processes.length; i++) {
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].name}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].memory}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].run_time}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].id}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].user_id}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.processes[i].virtual_memory}`));
tr.appendChild(td);
let process = pJson.processes[i];
create_table_element(
tr,
"",
[`${process.name}`, `${process.memory}`, `${process.run_time}`, `${process.id}`,
`${process.user_id}`, `${process.virtual_memory}`]
)
tableBody.appendChild(tr);
tr = document.createElement('tr');
@ -194,83 +185,74 @@ function createDisks(pJson) {
let tr = document.createElement('tr');
tableBody.appendChild(tr);
let td = document.createElement('td');
td.appendChild(document.createTextNode(`Name`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Type`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Total Space`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Available Space`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`Usage`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`File System`));
tr.appendChild(td);
create_table_element(
tr,
"",
["Name", "Type", "Total Space", "Available Space",
"Usage","File System"]
);
tableBody.appendChild(tr);
tr = document.createElement('tr');
for (let i = 0; i < pJson.disks.length; i++) {
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].name}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].disk_type}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].total_space}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].available_space}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].usage}`));
tr.appendChild(td);
td = document.createElement('td');
td.appendChild(document.createTextNode(`${pJson.disks[i].file_system}`));
tr.appendChild(td);
let disk = pJson.disks[i];
create_table_element(
tr,
"",
[`${disk.name}`, `${disk.disk_type}`, `${disk.total_space}`,
`${disk.available_space}`, `${disk.usage}%`, `${disk.file_system}`]
)
tableBody.appendChild(tr);
tr = document.createElement('tr');
}
div.appendChild(table);
};
function create_table_element(tr, string, elements) {
if (elements.length > 0) {
for (let i = 0; i < elements.length; i++) {
let td = document.createElement('td');
td.appendChild(document.createTextNode(elements[i]));
tr.appendChild(td);
}
} else {
let td = document.createElement('td');
td.appendChild(document.createTextNode(string));
tr.appendChild(td);
}
}
b = document.getElementById("Processes Header");
b.addEventListener("click", () => {
reset_tables();
processes = true;
disks = false;
sysinfo = false;
createElements(json[server]);
createElements(json);
});
c = document.getElementById("systeminfo");
c.addEventListener("click", () => {
processes = false;
disks = false;
reset_tables();
sysinfo = true;
createElements(json[server]);
createElements(json);
});
let button = document.getElementById
e = document.getElementById("Disks Header");
e.addEventListener("click", () => {
reset_tables();
disks = true;
processes = false;
sysinfo = false;
createElements(json[server]);
createElements(json);
});
f = document.getElementById("allSystemsButton");
f.addEventListener("click", () => {
reset_tables();
allSysInfo = true;
createElements(json);
})
function createButtons(pJson) {
let div = document.getElementById("systems");
@ -282,14 +264,13 @@ function createButtons(pJson) {
let system = pJson[i];
let button = document.createElement("button");
button.textContent = system.host_name.toUpperCase();
button.textContent = system.host_name;
button.id = i;
button.addEventListener("click", () => {
disks = false;
processes = false;
reset_tables();
sysinfo = true;
server = button.id
createElements(json[button.id]);
createElements(json);
});
div.appendChild(button);
}

View File

@ -16,7 +16,7 @@
class API {
constructor() {
this.url = "http://arlofilley.com/api/";
this.url = "https://arlofilley.com/api/";
// this is the url of the server
// this may have to change later on
}
@ -284,7 +284,7 @@ class API {
getTest() {
let xhr = new XMLHttpRequest();
xhr.open('GET', `${this.url}get_test/`);
xhr.open('GET', `${this.url}new_test/`);
xhr.send();
xhr.onload = () =>{
const effectiveWidth = (windowWidth - 200) / 13;