reverse proxy info
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.0.15"
|
version = "0.0.16"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+8
-1
@@ -177,6 +177,7 @@ async fn home(State(state): State<SharedState>) -> Response {
|
|||||||
&state.asset_version,
|
&state.asset_version,
|
||||||
state.registration_enabled,
|
state.registration_enabled,
|
||||||
&state.frontend_log_level,
|
&state.frontend_log_level,
|
||||||
|
state.upload_max_size_bytes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +191,7 @@ async fn pad(State(state): State<SharedState>, Path(slug): Path<String>) -> Resp
|
|||||||
&state.asset_version,
|
&state.asset_version,
|
||||||
state.registration_enabled,
|
state.registration_enabled,
|
||||||
&state.frontend_log_level,
|
&state.frontend_log_level,
|
||||||
|
state.upload_max_size_bytes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Ok(None) => error_response(
|
Ok(None) => error_response(
|
||||||
@@ -215,6 +217,7 @@ async fn public_page(State(state): State<SharedState>, Path(token): Path<String>
|
|||||||
&state.asset_version,
|
&state.asset_version,
|
||||||
state.registration_enabled,
|
state.registration_enabled,
|
||||||
&state.frontend_log_level,
|
&state.frontend_log_level,
|
||||||
|
state.upload_max_size_bytes,
|
||||||
),
|
),
|
||||||
Ok(None) => error_response(
|
Ok(None) => error_response(
|
||||||
StatusCode::NOT_FOUND,
|
StatusCode::NOT_FOUND,
|
||||||
@@ -245,6 +248,7 @@ async fn workspace(
|
|||||||
&state.asset_version,
|
&state.asset_version,
|
||||||
state.registration_enabled,
|
state.registration_enabled,
|
||||||
&state.frontend_log_level,
|
&state.frontend_log_level,
|
||||||
|
state.upload_max_size_bytes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Ok(None) => error_response(
|
Ok(None) => error_response(
|
||||||
@@ -297,6 +301,7 @@ async fn note(
|
|||||||
&state.asset_version,
|
&state.asset_version,
|
||||||
state.registration_enabled,
|
state.registration_enabled,
|
||||||
&state.frontend_log_level,
|
&state.frontend_log_level,
|
||||||
|
state.upload_max_size_bytes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Ok(None) => error_response(
|
Ok(None) => error_response(
|
||||||
@@ -390,10 +395,12 @@ fn versioned_html(
|
|||||||
asset_version: &str,
|
asset_version: &str,
|
||||||
registration_enabled: bool,
|
registration_enabled: bool,
|
||||||
frontend_log_level: &str,
|
frontend_log_level: &str,
|
||||||
|
upload_max_size_bytes: usize,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let frontend_config = format!(
|
let frontend_config = format!(
|
||||||
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}"}});</script>"#,
|
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{}}});</script>"#,
|
||||||
escape_js_string(frontend_log_level),
|
escape_js_string(frontend_log_level),
|
||||||
|
upload_max_size_bytes,
|
||||||
);
|
);
|
||||||
let html = template
|
let html = template
|
||||||
.replace("__ASSET_VERSION__", asset_version)
|
.replace("__ASSET_VERSION__", asset_version)
|
||||||
|
|||||||
@@ -1,6 +1,26 @@
|
|||||||
import { logDebug, logError, logWarn } from "./logger.js";
|
import { logDebug, logError, logWarn } from "./logger.js";
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(bytes % (1024 * 1024) ? 1 : 0)} MB`;
|
||||||
|
if (bytes >= 1024) return `${Math.ceil(bytes / 1024)} KB`;
|
||||||
|
return `${bytes} B`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUploadSize(body) {
|
||||||
|
if (!(body instanceof FormData)) return;
|
||||||
|
const maxBytes = Number(window.__RUSTPAD_CONFIG__?.uploadMaxSizeBytes || 0);
|
||||||
|
if (!Number.isFinite(maxBytes) || maxBytes <= 0) return;
|
||||||
|
for (const value of body.values()) {
|
||||||
|
if (value instanceof File && value.size > maxBytes) {
|
||||||
|
const error = new Error(`The selected file is ${formatBytes(value.size)}. The upload limit is ${formatBytes(maxBytes)}.`);
|
||||||
|
error.status = 413;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function api(path, options = {}) {
|
export async function api(path, options = {}) {
|
||||||
|
validateUploadSize(options.body);
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), 12000);
|
const timeout = setTimeout(() => controller.abort(), 12000);
|
||||||
try {
|
try {
|
||||||
@@ -24,6 +44,9 @@ export async function api(path, options = {}) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === "AbortError") { logWarn("api.timeout", { method: options.method || "GET", path }); throw new Error("Timed out"); }
|
if (error.name === "AbortError") { logWarn("api.timeout", { method: options.method || "GET", path }); throw new Error("Timed out"); }
|
||||||
logError("api.network_error", error, { method: options.method || "GET", path });
|
logError("api.network_error", error, { method: options.method || "GET", path });
|
||||||
|
if (options.body instanceof FormData && error instanceof TypeError) {
|
||||||
|
throw new Error("Upload failed before the server returned a response. The file may exceed the server or proxy upload limit.");
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
} finally { clearTimeout(timeout); }
|
} finally { clearTimeout(timeout); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ export function applyFormat(editor, format) {
|
|||||||
if (format === "codeblock-lines") toggleWrap(editor, "```text=\n", "\n```", "code");
|
if (format === "codeblock-lines") toggleWrap(editor, "```text=\n", "\n```", "code");
|
||||||
if (format === "mermaid") toggleWrap(editor, "```mermaid\n", "\n```", "graph TD\n A[Start] --> B[End]");
|
if (format === "mermaid") toggleWrap(editor, "```mermaid\n", "\n```", "graph TD\n A[Start] --> B[End]");
|
||||||
if (format === "details") toggleWrap(editor, "<details>\n<summary>Click me</summary>\n\n", "\n</details>", "Content");
|
if (format === "details") toggleWrap(editor, "<details>\n<summary>Click me</summary>\n\n", "\n</details>", "Content");
|
||||||
if (format === "toc") toggleWrap(editor, "", "", "[TOC]");
|
if (format === "toc") {
|
||||||
|
const { start, end } = selection(editor);
|
||||||
|
const selected = editor.value.slice(start, end);
|
||||||
|
editor.setRangeText(selected || "[TOC]", start, end, selected ? "select" : "end");
|
||||||
|
}
|
||||||
if (format.startsWith("alert-")) {
|
if (format.startsWith("alert-")) {
|
||||||
const type = format.slice("alert-".length);
|
const type = format.slice("alert-".length);
|
||||||
toggleWrap(editor, `:::${type}\n`, "\n:::", "Alert content");
|
toggleWrap(editor, `:::${type}\n`, "\n:::", "Alert content");
|
||||||
|
|||||||
Reference in New Issue
Block a user