From 4f9d46cc28cddd7171949e9003dade37ece0216e Mon Sep 17 00:00:00 2001 From: xenofem <xenofem@xeno.science> Date: Thu, 26 May 2022 14:43:03 -0400 Subject: [PATCH 1/2] cachebusting and templating --- flake.nix | 3 +- src/main.rs | 49 ++++++++++++++------ static/404.html | 39 ---------------- static/index.html | 100 ---------------------------------------- templates/404.html | 24 ++++++++++ templates/base.html | 27 +++++++++++ templates/download.html | 91 ++++++++++++++++-------------------- templates/index.html | 87 ++++++++++++++++++++++++++++++++++ 8 files changed, 214 insertions(+), 206 deletions(-) delete mode 100644 static/404.html delete mode 100644 static/index.html create mode 100644 templates/404.html create mode 100644 templates/base.html create mode 100644 templates/index.html diff --git a/flake.nix b/flake.nix index ece7d09..f6fdb6f 100644 --- a/flake.nix +++ b/flake.nix @@ -42,7 +42,8 @@ buildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/${name} \ - --set TRANSBEAM_STATIC_DIR ${./static} + --set TRANSBEAM_STATIC_DIR ${./static} \ + --set TRANSBEAM_CACHEBUSTER ${builtins.substring 0 8 (builtins.hashString "sha256" (toString ./static))} ''; }; diff --git a/src/main.rs b/src/main.rs index 2fa3456..76ff8d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,8 @@ mod zip; use std::{fmt::Debug, path::PathBuf, str::FromStr}; -use actix_files::NamedFile; use actix_web::{ - error::InternalError, get, http::StatusCode, middleware::Logger, post, web, App, HttpRequest, + error::InternalError, get, middleware::Logger, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder, }; use actix_web_actors::ws; @@ -31,9 +30,9 @@ struct Config { max_lifetime: u16, upload_password: String, storage_dir: PathBuf, - static_dir: PathBuf, reverse_proxy: bool, mnemonic_codes: bool, + cachebuster: String, } pub fn get_ip_addr(req: &HttpRequest, reverse_proxy: bool) -> String { @@ -51,14 +50,29 @@ pub fn log_auth_failure(ip_addr: &str) { warn!("Incorrect authentication attempt from {}", ip_addr); } +#[derive(Template)] +#[template(path = "index.html")] +struct IndexPage<'a> { cachebuster: &'a str } + +#[get("/")] +async fn index(data: web::Data<AppState>) -> impl Responder { + HttpResponse::Ok().body(IndexPage { cachebuster: &data.config.cachebuster }.render().unwrap()) +} + #[derive(Deserialize)] struct DownloadRequest { code: String, download: Option<download::DownloadSelection>, } -#[derive(Serialize, Template)] +#[derive(Template)] #[template(path = "download.html")] +struct DownloadPage<'a> { + info: DownloadInfo, + cachebuster: &'a str, +} + +#[derive(Serialize)] struct DownloadInfo { file: StoredFile, code: String, @@ -105,11 +119,14 @@ async fn handle_download( } else { let offsets = info.contents.as_deref().map(zip::file_data_offsets); Ok(HttpResponse::Ok().body( - DownloadInfo { - file: info, - code: code.clone(), - available: file.metadata().await?.len(), - offsets, + DownloadPage { + info: DownloadInfo { + file: info, + code: code.clone(), + available: file.metadata().await?.len(), + offsets, + }, + cachebuster: &data.config.cachebuster, } .render() .unwrap(), @@ -149,6 +166,10 @@ async fn download_info( })) } +#[derive(Template)] +#[template(path = "404.html")] +struct NotFoundPage<'a> { cachebuster: &'a str } + fn not_found<T>(req: HttpRequest, data: web::Data<AppState>, report: bool) -> actix_web::Result<T> { if report { let ip_addr = get_ip_addr(&req, data.config.reverse_proxy); @@ -156,9 +177,7 @@ fn not_found<T>(req: HttpRequest, data: web::Data<AppState>, report: bool) -> ac } Err(InternalError::from_response( "Download not found", - NamedFile::open(data.config.static_dir.join("404.html"))? - .set_status_code(StatusCode::NOT_FOUND) - .into_response(&req), + HttpResponse::NotFound().body(NotFoundPage { cachebuster: &data.config.cachebuster }.render().unwrap()), ) .into()) } @@ -258,6 +277,7 @@ async fn main() -> std::io::Result<()> { env_or::<ByteSize>("TRANSBEAM_MAX_STORAGE_SIZE", ByteSize(64 * bytesize::GB)).as_u64(); let upload_password: String = std::env::var("TRANSBEAM_UPLOAD_PASSWORD").expect("TRANSBEAM_UPLOAD_PASSWORD must be set!"); + let cachebuster: String = env_or_else("TRANSBEAM_CACHEBUSTER", String::new); let data = web::Data::new(AppState { file_store: RwLock::new(FileStore::load(storage_dir.clone(), max_storage_size).await?), @@ -266,9 +286,9 @@ async fn main() -> std::io::Result<()> { max_lifetime, upload_password, storage_dir, - static_dir: static_dir.clone(), reverse_proxy, mnemonic_codes, + cachebuster, }, }); start_reaper(data.clone()); @@ -281,12 +301,13 @@ async fn main() -> std::io::Result<()> { } else { Logger::default() }) + .service(index) .service(handle_download) .service(download_info) .service(handle_upload) .service(check_upload_password) .service(upload_limits) - .service(actix_files::Files::new("/", static_dir.clone()).index_file("index.html")) + .service(actix_files::Files::new("/", static_dir.clone())) }); if reverse_proxy { diff --git a/static/404.html b/static/404.html deleted file mode 100644 index bfc07c8..0000000 --- a/static/404.html +++ /dev/null @@ -1,39 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <link rel="stylesheet" type="text/css" href="css/transbeam.css"/> - <link rel="stylesheet" type="text/css" href="css/colors.css"/> - <link rel="apple-touch-icon" href="images/site-icons/transbeam-apple.png"/> - <link rel="manifest" href="manifest.json"/> - <script src="js/download.js"></script> - <title>transbeam</title> - </head> - <body> - <div id="header"> - <a href="./"> - <img src="images/site-icons/transbeam.svg" height="128"> - <h1>transbeam</h1> - </a> - </div> - <div id="download" class="section"> - <h3>The download code you entered wasn't found. The download may have expired.</h3> - <form id="download_form" action="download" method="get"> - <div> - <label> - <input type="text" id="download_code_input" name="code" placeholder="Download code"/> - </label> - </div> - <input id="download_button" type="submit" value="Download"/> - </form> - </div> - <div class="section"> - <a href="./"><h3>< Back</h3></a> - </div> - <div id="footer"> - <h5>(c) 2022 xenofem, MIT licensed</h5> - <h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5> - </div> - </body> -</html> diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 3719884..0000000 --- a/static/index.html +++ /dev/null @@ -1,100 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <link rel="stylesheet" type="text/css" href="css/transbeam.css"/> - <link rel="stylesheet" type="text/css" href="css/states.css"/> - <link rel="stylesheet" type="text/css" href="css/colors.css"/> - <link rel="apple-touch-icon" href="images/site-icons/transbeam-apple.png"/> - <link rel="manifest" href="manifest.json"/> - <script src="js/util.js"></script> - <script src="js/download-landing.js"></script> - <script src="js/upload.js"></script> - <title>transbeam</title> - </head> - <body class="noscript landing"> - <div id="header"> - <img src="images/site-icons/transbeam.svg" height="128"> - <h1>transbeam</h1> - </div> - <div id="download" class="section"> - <h3 class="section_heading">Download</h3> - <form id="download_form" action="download" method="get"> - <div> - <label> - <input type="text" id="download_code_input" name="code" placeholder="Download code"/> - </label> - </div> - <input id="download_button" type="submit" value="Download"/> - </form> - </div> - <noscript>Javascript is required to upload files :(</noscript> - <div id="uploads_closed_notice" class="section"> - <h4>Uploading is currently closed.</h4> - </div> - <div id="upload" class="section"> - <h3 class="section_heading">Upload</h3> - <div id="message"></div> - <div> - <form id="upload_password_form"> - <div> - <label> - <input id="upload_password" type="password" placeholder="Password"/> - </label> - </div> - <div> - <input type="submit" id="submit_upload_password" value="Submit" /> - </div> - </form> - </div> - <div id="upload_controls"> - <div id="upload_settings"> - <div> - <button id="upload_button">Upload</button> - </div> - <div id="lifetime_container"> - <label> - Keep files for: - <select id="lifetime"> - <option value="1">1 day</option> - <option value="7">1 week</option> - <option value="14" selected>2 weeks</option> - <option value="30">1 month</option> - <option value="60">2 months</option> - <option value="90">3 months</option> - <option value="180">6 months</option> - <option value="365">1 year</option> - </select> - </label> - </div> - </div> - <div id="download_code_container"> - <div id="download_code_main"> - <div>Download code: <span id="download_code"></span></div><div class="copy_button"></div> - </div> - <div id="copied_message">Link copied!</div> - </div> - <div id="progress_container"> - <div id="progress"> - <div id="progress_percentage"></div> - <div id="progress_size"></div> - <div id="progress_rate"></div> - <div id="progress_eta"></div> - </div> - <div id="progress_bar"></div> - </div> - <table id="file_list"> - </table> - <label id="file_input_container"> - <input type="file" multiple id="file_input"/> - <span class="fake_button" id="file_input_message">Select files to upload...</span> - </label> - </div> - </div> - <div id="footer"> - <h5>(c) 2022 xenofem, MIT licensed</h5> - <h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5> - </div> - </body> -</html> diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..55979fa --- /dev/null +++ b/templates/404.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}Download not found - transbeam{% endblock %} + +{% block head %} + <script src="js/download-landing.js?{{ cachebuster }}"></script> +{% endblock %} + +{% block body %} + <div id="download" class="section"> + <h3>The download code you entered wasn't found. The download may have expired.</h3> + <form id="download_form" action="download" method="get"> + <div> + <label> + <input type="text" id="download_code_input" name="code" placeholder="Download code"/> + </label> + </div> + <input id="download_button" type="submit" value="Download"/> + </form> + </div> + <div class="section"> + <a href="./"><h3>< Back</h3></a> + </div> +{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..0bc7050 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <meta name="color-scheme" content="light dark"> + <link rel="stylesheet" type="text/css" href="css/transbeam.css?{{ cachebuster }}"/> + <link rel="stylesheet" type="text/css" href="css/colors.css?{{ cachebuster }}"/> + <link rel="apple-touch-icon" href="images/site-icons/transbeam-apple.png"/> + <link rel="manifest" href="manifest.json"/> + <title>{% block title %}transbeam{% endblock %}</title> + {% block head %}{% endblock %} + </head> + <body {% block body_attrs %}{% endblock %}> + <div id="header"> + <a href="./"> + <img src="images/site-icons/transbeam.svg" height="128"> + <h1>transbeam</h1> + </a> + </div> + {% block body %}{% endblock %} + <div id="footer"> + <h5>(c) 2022 xenofem, MIT licensed</h5> + <h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5> + </div> + </body> +</html> diff --git a/templates/download.html b/templates/download.html index a834c2a..8675972 100644 --- a/templates/download.html +++ b/templates/download.html @@ -1,52 +1,39 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"/> - <link rel="stylesheet" type="text/css" href="css/transbeam.css"/> - <link rel="stylesheet" type="text/css" href="css/colors.css"/> - <link rel="apple-touch-icon" href="images/site-icons/transbeam-apple.png"/> - <link rel="manifest" href="manifest.json"/> - <script src="js/util.js"></script> - <script type="text/javascript"> - const CODE = "{{ code }}"; - </script> - <script src="js/download.js"></script> - <title>{{ file.name }} - transbeam</title> - </head> - <body> - <div id="header"> - <img src="images/site-icons/transbeam.svg" height="128"> - <h1>transbeam</h1> - </div> - <div id="download_toplevel" class="section"> - <div class="file_name">{{ file.name }}</div> - <div class="file_size">{{ bytesize::to_string(file.size.clone(), false).replace(" ", "") }}</div> - <div class="file_download"><a class="download_button" href="download?code={{ code }}&download=all"></a></div> - </div> - {% match file.contents %} - {% when Some with (files) %} - <div id="download_contents" class="section"> - <details> - <summary>Show file list</summary> - <table><tbody> - {% let offsets = offsets.as_ref().unwrap() %} - {% for f in files %} - <tr class="{% if offsets.get(loop.index0.clone()).unwrap().clone() > available %}unavailable{% endif %}"> - <td class="file_size">{{ bytesize::to_string(f.size.clone(), false).replace(" ", "") }}</td> - <td class="file_name">{{ f.name }}</td> - <td class="file_download"><a class="download_button" href="download?code={{ code }}&download={{ loop.index0 }}"></a></td> - <td class="file_unavailable"></td> - </tr> - {% endfor %} - </tbody></table> - </details> - </div> - {% else %} - {% endmatch %} - <div id="footer"> - <h5>(c) 2022 xenofem, MIT licensed</h5> - <h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5> - </div> - </body> -</html> +{% extends "base.html" %} + +{% block title %}{{ info.file.name }} - transbeam{% endblock %} + +{% block head %} + <script src="js/util.js?{{ cachebuster }}"></script> + <script type="text/javascript"> + const CODE = "{{ info.code }}"; + </script> + <script src="js/download.js?{{ cachebuster }}"></script> +{% endblock %} + +{% block body %} + <div id="download_toplevel" class="section"> + <div class="file_name">{{ info.file.name }}</div> + <div class="file_size">{{ bytesize::to_string(info.file.size.clone(), false).replace(" ", "") }}</div> + <div class="file_download"><a class="download_button" href="download?code={{ info.code }}&download=all"></a></div> + </div> + {% match info.file.contents %} + {% when Some with (files) %} + <div id="download_contents" class="section"> + <details> + <summary>Show file list</summary> + <table><tbody> + {% let offsets = info.offsets.as_ref().unwrap() %} + {% for f in files %} + <tr class="{% if offsets.get(loop.index0.clone()).unwrap().clone() > info.available %}unavailable{% endif %}"> + <td class="file_size">{{ bytesize::to_string(f.size.clone(), false).replace(" ", "") }}</td> + <td class="file_name">{{ f.name }}</td> + <td class="file_download"><a class="download_button" href="download?code={{ info.code }}&download={{ loop.index0 }}"></a></td> + <td class="file_unavailable"></td> + </tr> + {% endfor %} + </tbody></table> + </details> + </div> + {% else %} + {% endmatch %} +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..81b3612 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} + +{% block head %} + <link rel="stylesheet" type="text/css" href="css/states.css?{{ cachebuster }}"/> + <script src="js/util.js?{{ cachebuster }}"></script> + <script src="js/download-landing.js?{{ cachebuster }}"></script> + <script src="js/upload.js?{{ cachebuster }}"></script> +{% endblock %} + +{% block body_attrs %}class="noscript landing"{% endblock %} + +{% block body %} + <div id="download" class="section"> + <h3 class="section_heading">Download</h3> + <form id="download_form" action="download" method="get"> + <div> + <label> + <input type="text" id="download_code_input" name="code" placeholder="Download code"/> + </label> + </div> + <input id="download_button" type="submit" value="Download"/> + </form> + </div> + <noscript>Javascript is required to upload files :(</noscript> + <div id="uploads_closed_notice" class="section"> + <h4>Uploading is currently closed.</h4> + </div> + <div id="upload" class="section"> + <h3 class="section_heading">Upload</h3> + <div id="message"></div> + <div> + <form id="upload_password_form"> + <div> + <label> + <input id="upload_password" type="password" placeholder="Password"/> + </label> + </div> + <div> + <input type="submit" id="submit_upload_password" value="Submit" /> + </div> + </form> + </div> + <div id="upload_controls"> + <div id="upload_settings"> + <div> + <button id="upload_button">Upload</button> + </div> + <div id="lifetime_container"> + <label> + Keep files for: + <select id="lifetime"> + <option value="1">1 day</option> + <option value="7">1 week</option> + <option value="14" selected>2 weeks</option> + <option value="30">1 month</option> + <option value="60">2 months</option> + <option value="90">3 months</option> + <option value="180">6 months</option> + <option value="365">1 year</option> + </select> + </label> + </div> + </div> + <div id="download_code_container"> + <div id="download_code_main"> + <div>Download code: <span id="download_code"></span></div><div class="copy_button"></div> + </div> + <div id="copied_message">Link copied!</div> + </div> + <div id="progress_container"> + <div id="progress"> + <div id="progress_percentage"></div> + <div id="progress_size"></div> + <div id="progress_rate"></div> + <div id="progress_eta"></div> + </div> + <div id="progress_bar"></div> + </div> + <table id="file_list"> + </table> + <label id="file_input_container"> + <input type="file" multiple id="file_input"/> + <span class="fake_button" id="file_input_message">Select files to upload...</span> + </label> + </div> + </div> +{% endblock %} From 70e6b8bec65b84f004675bb51ddf8e5889626262 Mon Sep 17 00:00:00 2001 From: xenofem <xenofem@xeno.science> Date: Thu, 26 May 2022 14:43:21 -0400 Subject: [PATCH 2/2] clean up css color scheme stuff a bit --- static/css/colors.css | 52 +++++++++++++++++++--------------------- static/css/transbeam.css | 1 + 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/static/css/colors.css b/static/css/colors.css index 65bd95a..e9914ac 100644 --- a/static/css/colors.css +++ b/static/css/colors.css @@ -1,35 +1,31 @@ -@media not all and (prefers-color-scheme: dark) { - :root { - color-scheme: light; - --success-primary: #060; - --success-secondary: #0a0; - --failure-primary: #d00; - --failure-secondary: #f24; - --progress-primary: #27f; - --progress-secondary: #48f; - --progress-tertiary: #7af; - --border: #ddd; - --border-active: #777; - --icon-primary: #333; - --icon-primary-active: #000; - --icon-secondary: #888; - --icon-warning: #f00; - --icon-ready: #33f; - --icon-ready-active: #00b; - --icon-unavailable: #999; - --button-text: #000; - --button-background: #ccc; - --button-background-active: #aaa; - --button-border: #bbb; - --button-disabled-text: #666; - --button-disabled-background: #eee; - --button-disabled-border: #ddd; - } +:root { + --success-primary: #060; + --success-secondary: #0a0; + --failure-primary: #d00; + --failure-secondary: #f24; + --progress-primary: #27f; + --progress-secondary: #48f; + --progress-tertiary: #7af; + --border: #ddd; + --border-active: #777; + --icon-primary: #333; + --icon-primary-active: #000; + --icon-secondary: #888; + --icon-warning: #f00; + --icon-ready: #33f; + --icon-ready-active: #00b; + --icon-unavailable: #999; + --button-text: #000; + --button-background: #ccc; + --button-background-active: #aaa; + --button-border: #bbb; + --button-disabled-text: #666; + --button-disabled-background: #eee; + --button-disabled-border: #ddd; } @media (prefers-color-scheme: dark) { :root { - color-scheme: dark; --success-primary: #0d0; --success-secondary: #080; --failure-primary: #f34; diff --git a/static/css/transbeam.css b/static/css/transbeam.css index 92361c0..c0d48c9 100644 --- a/static/css/transbeam.css +++ b/static/css/transbeam.css @@ -9,6 +9,7 @@ body { #header h1 { margin-top: 5px; + color: CanvasText; } #header a {