Add realtime stats for filesystem

This commit is contained in:
2023-05-19 21:45:42 +02:00
parent 98c725f291
commit 794a38da19
8 changed files with 133 additions and 7 deletions

23
res/template/403.html Normal file
View File

@@ -0,0 +1,23 @@
{{define "403"}}<!DOCTYPE html>
<html lang="en">
<head>
{{template "meta_tags" "Forbidden"}}
</head>
<body>
{{template "page_top" .}}
<header>
<h1>Forbidden</h1>
</header>
<div id="page_content" class="page_content">
<section>
<p>
You do not have permission to access this resource.
</p>
</section>
</div>
{{template "page_bottom" .}}
{{template "analytics"}}
</body>
</html>
{{end}}

View File

@@ -7,6 +7,7 @@ export let file = {
size: 0, size: 0,
downloads: 0, downloads: 0,
bandwidth_used: 0, bandwidth_used: 0,
bandwidth_used_paid: 0,
} }
let views = 0 let views = 0

View File

@@ -22,7 +22,6 @@ export const edit = (f, t = "file") => {
shared = !(file.id === undefined || file.id === "") shared = !(file.id === undefined || file.id === "")
read_password = file.read_password ? file.read_password : "" read_password = file.read_password ? file.read_password : ""
write_password = file.write_password ? file.write_password : "" write_password = file.write_password ? file.write_password : ""
mode = file.mode_octal
visible = true visible = true
} }
@@ -32,7 +31,6 @@ let file_name = ""
let shared = false let shared = false
let read_password = "" let read_password = ""
let write_password = "" let write_password = ""
let mode = ""
const save = async () => { const save = async () => {
console.debug("Saving file", file.path) console.debug("Saving file", file.path)
@@ -41,7 +39,6 @@ const save = async () => {
bucket, bucket,
file.path, file.path,
{ {
mode: mode,
shared: shared, shared: shared,
read_password: read_password, read_password: read_password,
write_password: write_password, write_password: write_password,
@@ -95,8 +92,6 @@ const delete_file = async () => {
<span class="header">File settings</span> <span class="header">File settings</span>
<label for="file_name">Name:</label> <label for="file_name">Name:</label>
<input bind:value={file_name} id="file_name" type="text" class="form_input"/> <input bind:value={file_name} id="file_name" type="text" class="form_input"/>
<label for="mode">Mode:</label>
<input bind:value={mode} id="mode" type="text" class="form_input"/>
<span class="header">Delete</span> <span class="header">Delete</span>
<p> <p>
Delete this file or directory. If this is a directory then all Delete this file or directory. If this is a directory then all
@@ -133,7 +128,7 @@ const delete_file = async () => {
<style> <style>
.header { .header {
margin: 0.5em 0; margin-top: 1em;
font-size: 1.5em; font-size: 1.5em;
border-bottom: 1px var(--separator) solid; border-bottom: 1px var(--separator) solid;
} }

View File

@@ -0,0 +1,93 @@
<script>
import { onDestroy } from "svelte";
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
import { fs_path_url } from "./FilesystemUtil";
export let state
let downloads = 0
let bandwidth_used = 0
let socket = null
let error_msg = "Loading..."
let connected_to = ""
$: update_base(state.base)
const update_base = async base => {
if (connected_to === base.path) {
return
}
connected_to = base.path
// If the socket is already active we need to close it
close_socket()
error_msg = "Loading..."
let ws_endpoint = location.origin.replace(/^http/, 'ws') +
fs_path_url(state.root.id, base.path).replace(/^http/, 'ws') +
"?download_stats"
console.log("Opening socket to", ws_endpoint)
socket = new WebSocket(ws_endpoint)
socket.onmessage = msg => {
let j = JSON.parse(msg.data)
console.debug("WS update", j)
error_msg = ""
downloads = j.downloads
bandwidth_used = j.bandwidth_free + j.bandwidth_paid
}
socket.onerror = err => {
if (socket === null) {
return
}
console.error("WS error", err)
socket.close()
socket = null
error_msg = "failed to get stats, retrying..."
window.setTimeout(() => {
if (socket === null) {
state_update()
}
}, 3000)
}
}
const close_socket = () => {
if (socket !== null) {
// Disable the error handler so it doesn't start retrying the connection
socket.onerror = null
socket.close()
socket = null
}
}
onDestroy(close_socket)
</script>
<div>
{#if error_msg !== ""}
{error_msg}
{:else}
<div class="label">Downloads</div>
<div class="stat">{formatThousands(downloads)}</div>
<div class="label">Bandwidth used</div>
<div class="stat">{formatDataVolume(bandwidth_used, 3)}</div>
{/if}
</div>
<style>
.label {
text-align: left;
padding-left: 10px;
font-size: 0.8em;
line-height: 0.7em;
margin-top: 0.5em;
}
.stat {
text-align: center;
}
</style>

View File

@@ -37,6 +37,7 @@ export const navigate = async (path, push_history) => {
let resp = await fs_get_node(state.root.id, path) let resp = await fs_get_node(state.root.id, path)
open_node(resp, push_history) open_node(resp, push_history)
} catch (err) { } catch (err) {
console.error(err)
let errj = JSON.parse(err) let errj = JSON.parse(err)
if (errj.value === "path_not_found") { if (errj.value === "path_not_found") {

View File

@@ -3,6 +3,7 @@ import { createEventDispatcher } from "svelte";
import Sharebar, { generate_share_url } from "./Sharebar.svelte"; import Sharebar, { generate_share_url } from "./Sharebar.svelte";
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"; import { formatDataVolume, formatThousands } from "../util/Formatting.svelte";
import { copy_text } from "../util/Util.svelte"; import { copy_text } from "../util/Util.svelte";
import FileStats from "./FileStats.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -66,6 +67,8 @@ let share = async () => {
<div class="toolbar" class:toolbar_visible={visible}> <div class="toolbar" class:toolbar_visible={visible}>
{#if state.base.type === "file"} {#if state.base.type === "file"}
<FileStats state={state}/>
<div class="toolbar_label">Size</div> <div class="toolbar_label">Size</div>
<div class="toolbar_statistic">{formatDataVolume(state.base.file_size, 3)}</div> <div class="toolbar_statistic">{formatDataVolume(state.base.file_size, 3)}</div>
{:else if state.base.type === "dir" || state.base.type === "bucket"} {:else if state.base.type === "dir" || state.base.type === "bucket"}

View File

@@ -25,7 +25,11 @@ func (wc *WebController) serveDirectory(w http.ResponseWriter, r *http.Request,
node, err := td.PixelAPI.GetFilesystemPath(path) node, err := td.PixelAPI.GetFilesystemPath(path)
if err != nil { if err != nil {
if err.Error() == "not_found" || err.Error() == "path_not_found" { if err.Error() == "not_found" || err.Error() == "path_not_found" {
wc.templates.Get().ExecuteTemplate(w, "404", td) wc.serveNotFound(w, r)
return
} else if err.Error() == "forbidden" {
wc.serveForbidden(w, r)
return
} else if err.Error() == "authentication_required" { } else if err.Error() == "authentication_required" {
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} else { } else {

View File

@@ -387,6 +387,12 @@ func (wc *WebController) serveForm(
} }
} }
func (wc *WebController) serveForbidden(w http.ResponseWriter, r *http.Request) {
log.Debug("Forbidden: %s", r.URL)
w.WriteHeader(http.StatusForbidden)
wc.templates.Get().ExecuteTemplate(w, "403", wc.newTemplateData(w, r))
}
func (wc *WebController) serveNotFound(w http.ResponseWriter, r *http.Request) { func (wc *WebController) serveNotFound(w http.ResponseWriter, r *http.Request) {
log.Debug("Not Found: %s", r.URL) log.Debug("Not Found: %s", r.URL)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)