cachebusting and templating
This commit is contained in:
parent
b90220da22
commit
4f9d46cc28
|
@ -42,7 +42,8 @@
|
||||||
buildInputs = [ pkgs.makeWrapper ];
|
buildInputs = [ pkgs.makeWrapper ];
|
||||||
postBuild = ''
|
postBuild = ''
|
||||||
wrapProgram $out/bin/${name} \
|
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))}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -6,9 +6,8 @@ mod zip;
|
||||||
|
|
||||||
use std::{fmt::Debug, path::PathBuf, str::FromStr};
|
use std::{fmt::Debug, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use actix_files::NamedFile;
|
|
||||||
use actix_web::{
|
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,
|
HttpResponse, HttpServer, Responder,
|
||||||
};
|
};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
|
@ -31,9 +30,9 @@ struct Config {
|
||||||
max_lifetime: u16,
|
max_lifetime: u16,
|
||||||
upload_password: String,
|
upload_password: String,
|
||||||
storage_dir: PathBuf,
|
storage_dir: PathBuf,
|
||||||
static_dir: PathBuf,
|
|
||||||
reverse_proxy: bool,
|
reverse_proxy: bool,
|
||||||
mnemonic_codes: bool,
|
mnemonic_codes: bool,
|
||||||
|
cachebuster: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ip_addr(req: &HttpRequest, reverse_proxy: bool) -> 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);
|
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)]
|
#[derive(Deserialize)]
|
||||||
struct DownloadRequest {
|
struct DownloadRequest {
|
||||||
code: String,
|
code: String,
|
||||||
download: Option<download::DownloadSelection>,
|
download: Option<download::DownloadSelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "download.html")]
|
#[template(path = "download.html")]
|
||||||
|
struct DownloadPage<'a> {
|
||||||
|
info: DownloadInfo,
|
||||||
|
cachebuster: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
struct DownloadInfo {
|
struct DownloadInfo {
|
||||||
file: StoredFile,
|
file: StoredFile,
|
||||||
code: String,
|
code: String,
|
||||||
|
@ -105,11 +119,14 @@ async fn handle_download(
|
||||||
} else {
|
} else {
|
||||||
let offsets = info.contents.as_deref().map(zip::file_data_offsets);
|
let offsets = info.contents.as_deref().map(zip::file_data_offsets);
|
||||||
Ok(HttpResponse::Ok().body(
|
Ok(HttpResponse::Ok().body(
|
||||||
DownloadInfo {
|
DownloadPage {
|
||||||
|
info: DownloadInfo {
|
||||||
file: info,
|
file: info,
|
||||||
code: code.clone(),
|
code: code.clone(),
|
||||||
available: file.metadata().await?.len(),
|
available: file.metadata().await?.len(),
|
||||||
offsets,
|
offsets,
|
||||||
|
},
|
||||||
|
cachebuster: &data.config.cachebuster,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.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> {
|
fn not_found<T>(req: HttpRequest, data: web::Data<AppState>, report: bool) -> actix_web::Result<T> {
|
||||||
if report {
|
if report {
|
||||||
let ip_addr = get_ip_addr(&req, data.config.reverse_proxy);
|
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(
|
Err(InternalError::from_response(
|
||||||
"Download not found",
|
"Download not found",
|
||||||
NamedFile::open(data.config.static_dir.join("404.html"))?
|
HttpResponse::NotFound().body(NotFoundPage { cachebuster: &data.config.cachebuster }.render().unwrap()),
|
||||||
.set_status_code(StatusCode::NOT_FOUND)
|
|
||||||
.into_response(&req),
|
|
||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
@ -258,6 +277,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
env_or::<ByteSize>("TRANSBEAM_MAX_STORAGE_SIZE", ByteSize(64 * bytesize::GB)).as_u64();
|
env_or::<ByteSize>("TRANSBEAM_MAX_STORAGE_SIZE", ByteSize(64 * bytesize::GB)).as_u64();
|
||||||
let upload_password: String =
|
let upload_password: String =
|
||||||
std::env::var("TRANSBEAM_UPLOAD_PASSWORD").expect("TRANSBEAM_UPLOAD_PASSWORD must be set!");
|
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 {
|
let data = web::Data::new(AppState {
|
||||||
file_store: RwLock::new(FileStore::load(storage_dir.clone(), max_storage_size).await?),
|
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,
|
max_lifetime,
|
||||||
upload_password,
|
upload_password,
|
||||||
storage_dir,
|
storage_dir,
|
||||||
static_dir: static_dir.clone(),
|
|
||||||
reverse_proxy,
|
reverse_proxy,
|
||||||
mnemonic_codes,
|
mnemonic_codes,
|
||||||
|
cachebuster,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
start_reaper(data.clone());
|
start_reaper(data.clone());
|
||||||
|
@ -281,12 +301,13 @@ async fn main() -> std::io::Result<()> {
|
||||||
} else {
|
} else {
|
||||||
Logger::default()
|
Logger::default()
|
||||||
})
|
})
|
||||||
|
.service(index)
|
||||||
.service(handle_download)
|
.service(handle_download)
|
||||||
.service(download_info)
|
.service(download_info)
|
||||||
.service(handle_upload)
|
.service(handle_upload)
|
||||||
.service(check_upload_password)
|
.service(check_upload_password)
|
||||||
.service(upload_limits)
|
.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 {
|
if reverse_proxy {
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
24
templates/404.html
Normal file
24
templates/404.html
Normal file
|
@ -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 %}
|
27
templates/base.html
Normal file
27
templates/base.html
Normal file
|
@ -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>
|
|
@ -1,41 +1,33 @@
|
||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}{{ info.file.name }} - transbeam{% endblock %}
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
{% block head %}
|
||||||
<link rel="stylesheet" type="text/css" href="css/transbeam.css"/>
|
<script src="js/util.js?{{ cachebuster }}"></script>
|
||||||
<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">
|
<script type="text/javascript">
|
||||||
const CODE = "{{ code }}";
|
const CODE = "{{ info.code }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="js/download.js"></script>
|
<script src="js/download.js?{{ cachebuster }}"></script>
|
||||||
<title>{{ file.name }} - transbeam</title>
|
{% endblock %}
|
||||||
</head>
|
|
||||||
<body>
|
{% block body %}
|
||||||
<div id="header">
|
|
||||||
<img src="images/site-icons/transbeam.svg" height="128">
|
|
||||||
<h1>transbeam</h1>
|
|
||||||
</div>
|
|
||||||
<div id="download_toplevel" class="section">
|
<div id="download_toplevel" class="section">
|
||||||
<div class="file_name">{{ file.name }}</div>
|
<div class="file_name">{{ info.file.name }}</div>
|
||||||
<div class="file_size">{{ bytesize::to_string(file.size.clone(), false).replace(" ", "") }}</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={{ code }}&download=all"></a></div>
|
<div class="file_download"><a class="download_button" href="download?code={{ info.code }}&download=all"></a></div>
|
||||||
</div>
|
</div>
|
||||||
{% match file.contents %}
|
{% match info.file.contents %}
|
||||||
{% when Some with (files) %}
|
{% when Some with (files) %}
|
||||||
<div id="download_contents" class="section">
|
<div id="download_contents" class="section">
|
||||||
<details>
|
<details>
|
||||||
<summary>Show file list</summary>
|
<summary>Show file list</summary>
|
||||||
<table><tbody>
|
<table><tbody>
|
||||||
{% let offsets = offsets.as_ref().unwrap() %}
|
{% let offsets = info.offsets.as_ref().unwrap() %}
|
||||||
{% for f in files %}
|
{% for f in files %}
|
||||||
<tr class="{% if offsets.get(loop.index0.clone()).unwrap().clone() > available %}unavailable{% endif %}">
|
<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_size">{{ bytesize::to_string(f.size.clone(), false).replace(" ", "") }}</td>
|
||||||
<td class="file_name">{{ f.name }}</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_download"><a class="download_button" href="download?code={{ info.code }}&download={{ loop.index0 }}"></a></td>
|
||||||
<td class="file_unavailable"></td>
|
<td class="file_unavailable"></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -44,9 +36,4 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
<div id="footer">
|
{% endblock %}
|
||||||
<h5>(c) 2022 xenofem, MIT licensed</h5>
|
|
||||||
<h5><a target="_blank" href="https://git.xeno.science/xenofem/transbeam">source</a></h5>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
87
templates/index.html
Normal file
87
templates/index.html
Normal file
|
@ -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 %}
|
Loading…
Reference in a new issue