Convert admin panel to svelte
This commit is contained in:
@@ -1,173 +1,33 @@
|
|||||||
{{define "admin_panel"}}
|
{{define "admin"}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
{{if and .Authenticated .User.IsAdmin}}
|
||||||
{{template "meta_tags" "Administrator panel"}}
|
<head>
|
||||||
{{template "user_style" .}}
|
{{template "meta_tags" "Administrator panel"}}
|
||||||
</head>
|
{{template "user_style" .}}
|
||||||
<body>
|
|
||||||
{{template "page_top" .}}
|
|
||||||
<div class="page_content">
|
|
||||||
{{if and .Authenticated .User.IsAdmin}}
|
|
||||||
<div class="limit_width">
|
|
||||||
<h3>Actions</h3>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<a class="button" href="/admin/abuse">
|
|
||||||
<i class="icon">block</i>
|
|
||||||
Block files
|
|
||||||
</a>
|
|
||||||
<a class="button" href="/admin/abuse_reports">
|
|
||||||
<i class="icon">flag</i>
|
|
||||||
User abuse reports
|
|
||||||
</a>
|
|
||||||
<a class="button" href="/admin/abuse_reporters">
|
|
||||||
<i class="icon">report</i>
|
|
||||||
Manage abuse reporters
|
|
||||||
</a>
|
|
||||||
<a class="button" href="/admin/globals">
|
|
||||||
<i class="icon">edit</i>
|
|
||||||
Update global settings
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
<div class="limit_width">
|
|
||||||
<h3>Bandwidth and views</h3>
|
|
||||||
</div>
|
|
||||||
<div class="highlight_dark">
|
|
||||||
<button onclick="loadGraph(1440, 1, true);">Day</button>
|
|
||||||
<button onclick="loadGraph(10080, 10, false);">Week</button>
|
|
||||||
<button onclick="loadGraph(20160, 60, false);">Two Weeks</button>
|
|
||||||
<button onclick="loadGraph(43200, 60, false);">Month</button>
|
|
||||||
<button onclick="loadGraph(131400, 1440, false);">Quarter</button>
|
|
||||||
<button onclick="loadGraph(262800, 1440, false);">Half-year</button>
|
|
||||||
<button onclick="loadGraph(525600, 1440, false);">Year</button>
|
|
||||||
<button onclick="loadGraph(1051200, 1440, false);">Two Years</button>
|
|
||||||
<!--<br/>
|
|
||||||
<button onclick="navigateTimespan(false);">🠈</button>
|
|
||||||
<button onclick="loadTimespan('day', new Date());">Day</button>
|
|
||||||
<button onclick="loadTimespan('week', new Date());">Week</button>
|
|
||||||
<button onclick="loadTimespan('month', new Date());">Month</button>
|
|
||||||
<button onclick="loadTimespan('quarter', new Date());">Quarter</button>
|
|
||||||
<button onclick="loadTimespan('year', new Date());">Year</button>
|
|
||||||
<button onclick="navigateTimespan(true);">🠊</button>-->
|
|
||||||
</div>
|
|
||||||
<div id="chart_container" class="chart-container" style="position: relative; width: 100%; height: 200px; padding-top: 6px;">
|
|
||||||
<canvas id="bandwidth_chart"></canvas>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div id="chart_container" class="chart-container" style="position: relative; width: 100%; height: 200px;">
|
|
||||||
<canvas id="views_chart"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="highlight_dark">
|
|
||||||
Total usage from <span id="time_start"></span> to <span id="time_end"></span><br/>
|
|
||||||
<span id="total_bandwidth"></span> bandwidth and <span id="total_views"></span> views
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
<script>
|
||||||
<a class="button" href="/api/admin/call_stack">Call stack</a>
|
window.api_endpoint = '{{.APIEndpoint}}';
|
||||||
<a class="button" href="/api/admin/heap_profile">Heap profile</a>
|
window.highlight_color = '#{{.Style.HighlightColor.RGB}}';
|
||||||
<a class="button" href="/api/admin/cpu_profile">CPU profile (wait 1 min)</a>
|
</script>
|
||||||
<br/>
|
<link rel='stylesheet' href='/res/svelte/admin_panel.css'>
|
||||||
|
<script defer src='/res/svelte/admin_panel.js'></script>
|
||||||
<div class="limit_width">
|
</head>
|
||||||
<table>
|
<body>
|
||||||
<tr>
|
{{template "page_top" .}}
|
||||||
<td>DB Time</td>
|
<div id="page_content" class="page_content"></div>
|
||||||
<td id="db_time"></td>
|
|
||||||
<td>DB Latency</td>
|
|
||||||
<td id="db_latency"></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<h3>Pixelstore peers</h3>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Address</td>
|
|
||||||
<td>Pos</td>
|
|
||||||
<td>Alive</td>
|
|
||||||
<td>Err</td>
|
|
||||||
<td>1m</td>
|
|
||||||
<td>5m</td>
|
|
||||||
<td>15m</td>
|
|
||||||
<td>Ping</td>
|
|
||||||
<td>Free</td>
|
|
||||||
<td>Min free</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tbody_peers"></tbody>
|
|
||||||
</table>
|
|
||||||
<h3>Pixelstore stats</h3>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Local reads</td>
|
|
||||||
<td>Local read size</td>
|
|
||||||
<td>Remote reads</td>
|
|
||||||
<td>Remote read size</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td id="local_reads"></td>
|
|
||||||
<td id="local_read_size"></td>
|
|
||||||
<td id="remote_reads"></td>
|
|
||||||
<td id="remote_read_size"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td id="local_reads_rate"></td>
|
|
||||||
<td id="local_read_size_rate"></td>
|
|
||||||
<td id="remote_reads_rate"></td>
|
|
||||||
<td id="remote_read_size_rate"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3>Websocket statistics</h3>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Watcher</td>
|
|
||||||
<td>Threads</td>
|
|
||||||
<td>Listeners</td>
|
|
||||||
<td>Avg</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>File statistics</td>
|
|
||||||
<td id="file_stats_watchers"></td>
|
|
||||||
<td id="file_stats_listeners"></td>
|
|
||||||
<td id="file_stats_avg"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3>Query statistics</h3>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td style="cursor: pointer;" onclick="getStats('query_name')">Query</td>
|
|
||||||
<td style="cursor: pointer;" onclick="getStats('calls')">Calls</td>
|
|
||||||
<td style="cursor: pointer;" onclick="getStats('average_duration')">Average Duration</td>
|
|
||||||
<td style="cursor: pointer;" onclick="getStats('total_duration')">Total Duration</td>
|
|
||||||
<td>Callers</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tstat_body"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<script src="/res/script/Chart.min.js"></script>
|
|
||||||
<script>
|
|
||||||
var apiEndpoint = '{{.APIEndpoint}}';
|
|
||||||
var highlightColor = '#{{.Style.HighlightColor.RGB}}';
|
|
||||||
{{template `util.js`}}
|
|
||||||
{{template `drawGraph.js`}}
|
|
||||||
{{template `admin.js`}}
|
|
||||||
</script>
|
|
||||||
{{else}}
|
|
||||||
<h1 style="text-align: center;">;-)</h1>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{template "page_bottom" .}}
|
{{template "page_bottom" .}}
|
||||||
</div>
|
</body>
|
||||||
</body>
|
{{else}}
|
||||||
|
<head>
|
||||||
|
{{template "meta_tags" "Administrator panel"}}
|
||||||
|
{{template "user_style" .}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{template "page_top" .}}
|
||||||
|
<div class="page_content">;-)</div>
|
||||||
|
{{template "page_bottom" .}}
|
||||||
|
</body>
|
||||||
|
{{end}}
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
{{define "admin_abuse_reporters"}}<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
{{template "meta_tags" "Abuse reporters"}}
|
|
||||||
{{template "user_style" .}}
|
|
||||||
<script>window.api_endpoint = '{{.APIEndpoint}}';</script>
|
|
||||||
<link rel='stylesheet' href='/res/svelte/admin_abuse_reporters.css'>
|
|
||||||
<script defer src='/res/svelte/admin_abuse_reporters.js'></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{template "page_top" .}}
|
|
||||||
<h1>Abuse reporters</h1>
|
|
||||||
<div id="page_content" class="page_content"></div>
|
|
||||||
|
|
||||||
{{template "page_bottom" .}}
|
|
||||||
{{template "analytics"}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
@@ -1,19 +0,0 @@
|
|||||||
{{define "admin_abuse_reports"}}<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
{{template "meta_tags" "Abuse reports"}}
|
|
||||||
{{template "user_style" .}}
|
|
||||||
<script>window.api_endpoint = '{{.APIEndpoint}}';</script>
|
|
||||||
<link rel='stylesheet' href='/res/svelte/admin_abuse_reports.css'>
|
|
||||||
<script defer src='/res/svelte/admin_abuse_reports.js'></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{template "page_top" .}}
|
|
||||||
<h1>Abuse reports</h1>
|
|
||||||
<div id="page_content" class="page_content"></div>
|
|
||||||
|
|
||||||
{{template "page_bottom" .}}
|
|
||||||
{{template "analytics"}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
@@ -1,19 +0,0 @@
|
|||||||
{{define "admin_ip_bans"}}<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
{{template "meta_tags" "IP bans"}}
|
|
||||||
{{template "user_style" .}}
|
|
||||||
<script>window.api_endpoint = '{{.APIEndpoint}}';</script>
|
|
||||||
<link rel='stylesheet' href='/res/svelte/admin_ip_bans.css'>
|
|
||||||
<script defer src='/res/svelte/admin_ip_bans.js'></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{template "page_top" .}}
|
|
||||||
<h1>IP bans</h1>
|
|
||||||
<div id="page_content" class="page_content"></div>
|
|
||||||
|
|
||||||
{{template "page_bottom" .}}
|
|
||||||
{{template "analytics"}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
@@ -16,6 +16,7 @@
|
|||||||
"svelte": "^3.0.0"
|
"svelte": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^2.8.0",
|
||||||
"sirv-cli": "^1.0.0"
|
"sirv-cli": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,9 +34,7 @@ export default [
|
|||||||
"modal",
|
"modal",
|
||||||
"user_buckets",
|
"user_buckets",
|
||||||
"user_file_manager",
|
"user_file_manager",
|
||||||
"admin_abuse_reporters",
|
"admin_panel",
|
||||||
"admin_abuse_reports",
|
|
||||||
"admin_ip_bans",
|
|
||||||
].map((name, index) => ({
|
].map((name, index) => ({
|
||||||
input: `src/${name}.js`,
|
input: `src/${name}.js`,
|
||||||
output: {
|
output: {
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
import App from './admin_abuse_reporters/AbuseReporters.svelte';
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.getElementById("page_content"),
|
|
||||||
props: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
@@ -1,8 +0,0 @@
|
|||||||
import App from './admin_abuse_reports/AbuseReports.svelte';
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.getElementById("page_content"),
|
|
||||||
props: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
@@ -1,4 +1,4 @@
|
|||||||
import App from './admin_ip_bans/IPBans.svelte';
|
import App from './admin_panel/AdminPanel.svelte';
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById("page_content"),
|
target: document.getElementById("page_content"),
|
@@ -91,9 +91,6 @@ onMount(get_reporters);
|
|||||||
|
|
||||||
<div class="limit_width">
|
<div class="limit_width">
|
||||||
<div class="toolbar" style="text-align: left;">
|
<div class="toolbar" style="text-align: left;">
|
||||||
<a class="button" href="/admin">
|
|
||||||
<i class="icon">arrow_back</i> Return to admin panel
|
|
||||||
</a>
|
|
||||||
<div class="toolbar_spacer"></div>
|
<div class="toolbar_spacer"></div>
|
||||||
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||||
<i class="icon">create</i> Add abuse reporter
|
<i class="icon">create</i> Add abuse reporter
|
||||||
@@ -171,6 +168,7 @@ onMount(get_reporters);
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
@@ -100,15 +100,12 @@ onMount(() => {
|
|||||||
|
|
||||||
<div class="limit_width">
|
<div class="limit_width">
|
||||||
<div class="toolbar" style="text-align: left;">
|
<div class="toolbar" style="text-align: left;">
|
||||||
<a class="button" href="/admin">
|
|
||||||
<i class="icon">arrow_back</i> Return to admin panel
|
|
||||||
</a>
|
|
||||||
<div class="toolbar_spacer"></div>
|
<div class="toolbar_spacer"></div>
|
||||||
<div>
|
<div>Start:</div>
|
||||||
Start: <input type="date" bind:this={startPicker}/>
|
<input type="date" bind:this={startPicker}/>
|
||||||
End: <input type="date" bind:this={endPicker}/>
|
<div>End:</div>
|
||||||
</div>
|
<input type="date" bind:this={endPicker}/>
|
||||||
<button on:click={get_reports}><i class="icon">refresh</i></button>
|
<button on:click={get_reports}>Go</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Pending</h2>
|
<h2>Pending</h2>
|
||||||
@@ -131,7 +128,7 @@ onMount(() => {
|
|||||||
.spinner_container {
|
.spinner_container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
left: 10px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@@ -140,6 +137,7 @@ onMount(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.toolbar > * { flex: 0 0 auto; }
|
.toolbar > * { flex: 0 0 auto; }
|
||||||
.toolbar_spacer { flex: 1 1 auto; }
|
.toolbar_spacer { flex: 1 1 auto; }
|
75
svelte/src/admin_panel/AdminPanel.svelte
Normal file
75
svelte/src/admin_panel/AdminPanel.svelte
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<script>
|
||||||
|
import AbuseReporters from "./AbuseReporters.svelte"
|
||||||
|
import AbuseReports from "./AbuseReports.svelte"
|
||||||
|
import IpBans from "./IPBans.svelte"
|
||||||
|
import Home from "./Home.svelte"
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let page = ""
|
||||||
|
|
||||||
|
let navigate = (path, title) => {
|
||||||
|
page = path
|
||||||
|
window.document.title = title+" ~ pixeldrain"
|
||||||
|
window.history.pushState(
|
||||||
|
{}, window.document.title, "/admin/"+path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let newpage = window.location.pathname.substring(window.location.pathname.lastIndexOf("/")+1)
|
||||||
|
if (newpage === "admin") {
|
||||||
|
newpage = ""
|
||||||
|
}
|
||||||
|
page = newpage
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a class="button"
|
||||||
|
href="/admin"
|
||||||
|
class:button_highlight={page === ""}
|
||||||
|
on:click|preventDefault={() => {navigate("", "Status")}}>
|
||||||
|
<i class="icon">home</i>
|
||||||
|
Status
|
||||||
|
</a>
|
||||||
|
<a class="button" href="/admin/abuse">
|
||||||
|
<i class="icon">block</i>
|
||||||
|
Block files
|
||||||
|
</a>
|
||||||
|
<a class="button"
|
||||||
|
href="/admin/abuse_reports"
|
||||||
|
class:button_highlight={page === "abuse_reports"}
|
||||||
|
on:click|preventDefault={() => {navigate("abuse_reports", "Abuse reports")}}>
|
||||||
|
<i class="icon">flag</i>
|
||||||
|
User abuse reports
|
||||||
|
</a>
|
||||||
|
<a class="button"
|
||||||
|
href="/admin/abuse_reporters"
|
||||||
|
class:button_highlight={page === "abuse_reporters"}
|
||||||
|
on:click|preventDefault={() => {navigate("abuse_reporters", "Abuse reporters")}}>
|
||||||
|
<i class="icon">report</i>
|
||||||
|
E-mail abuse reporters
|
||||||
|
</a>
|
||||||
|
<a class="button"
|
||||||
|
href="/admin/ip_bans"
|
||||||
|
class:button_highlight={page === "ip_bans"}
|
||||||
|
on:click|preventDefault={() => {navigate("ip_bans", "IP bans")}}>
|
||||||
|
<i class="icon">remove_circle</i>
|
||||||
|
IP bans
|
||||||
|
</a>
|
||||||
|
<a class="button" href="/admin/globals">
|
||||||
|
<i class="icon">edit</i>
|
||||||
|
Update global settings
|
||||||
|
</a>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
{#if page === ""}
|
||||||
|
<Home></Home>
|
||||||
|
{:else if page === "abuse_reports"}
|
||||||
|
<AbuseReports></AbuseReports>
|
||||||
|
{:else if page === "abuse_reporters"}
|
||||||
|
<AbuseReporters></AbuseReporters>
|
||||||
|
{:else if page === "ip_bans"}
|
||||||
|
<IpBans></IpBans>
|
||||||
|
{/if}
|
||||||
|
</div>
|
272
svelte/src/admin_panel/Home.svelte
Normal file
272
svelte/src/admin_panel/Home.svelte
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
<script>
|
||||||
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
import { formatDataVolume, formatThousands, formatDate, formatNumber, formatDuration } from "../util/Formatting.svelte";
|
||||||
|
import Chart from "../util/Chart.svelte";
|
||||||
|
|
||||||
|
let graphViews
|
||||||
|
let graphBandwidth
|
||||||
|
let graphTimeout = null
|
||||||
|
|
||||||
|
let start_time = ""
|
||||||
|
let end_time = ""
|
||||||
|
let total_bandwidth = 0
|
||||||
|
let total_views = 0
|
||||||
|
const loadGraph = (minutes, interval, live) => {
|
||||||
|
if (graphTimeout !== null) { clearTimeout(graphTimeout) }
|
||||||
|
if (live) {
|
||||||
|
graphTimeout = setTimeout(() => { loadGraph(minutes, interval, true) }, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
let today = new Date()
|
||||||
|
let start = new Date()
|
||||||
|
start.setMinutes(start.getMinutes() - minutes)
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
window.api_endpoint + "/admin/files/timeseries" +
|
||||||
|
"?start=" + start.toISOString() +
|
||||||
|
"&end=" + today.toISOString() +
|
||||||
|
"&interval=" + interval
|
||||||
|
).then(resp => {
|
||||||
|
if (!resp.ok) { return Promise.reject("Error: " + resp.status); }
|
||||||
|
return resp.json();
|
||||||
|
}).then(resp => {
|
||||||
|
resp.views.timestamps.forEach((val, idx) => {
|
||||||
|
let date = new Date(val);
|
||||||
|
let dateStr = ("00" + (date.getMonth() + 1)).slice(-2);
|
||||||
|
dateStr += "-" + ("00" + date.getDate()).slice(-2);
|
||||||
|
dateStr += " " + ("00" + date.getHours()).slice(-2);
|
||||||
|
dateStr += ":" + ("00" + date.getMinutes()).slice(-2);
|
||||||
|
resp.views.timestamps[idx] = " " + dateStr + " "; // Poor man's padding
|
||||||
|
});
|
||||||
|
graphViews.chart().data.labels = resp.views.timestamps;
|
||||||
|
graphViews.chart().data.datasets[0].data = resp.views.amounts;
|
||||||
|
graphBandwidth.chart().data.labels = resp.views.timestamps;
|
||||||
|
graphBandwidth.chart().data.datasets[0].data = resp.bandwidth.amounts;
|
||||||
|
graphViews.update()
|
||||||
|
graphBandwidth.update()
|
||||||
|
|
||||||
|
start_time = resp.views.timestamps[0]
|
||||||
|
end_time = resp.views.timestamps.slice(-1)[0];
|
||||||
|
total_bandwidth = resp.bandwidth.amounts.reduce((acc, val) => acc + val)
|
||||||
|
total_views = resp.views.amounts.reduce((acc, val) => acc + val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load performance statistics
|
||||||
|
|
||||||
|
let lastOrder;
|
||||||
|
let status = {
|
||||||
|
db_latency: 0,
|
||||||
|
db_time: "",
|
||||||
|
local_read_size: 0,
|
||||||
|
local_read_size_per_sec: 0,
|
||||||
|
local_reads: 0,
|
||||||
|
local_reads_per_sec: 0,
|
||||||
|
peers: [],
|
||||||
|
query_statistics: [],
|
||||||
|
remote_read_size: 0,
|
||||||
|
remote_read_size_per_sec: 0,
|
||||||
|
remote_reads: 0,
|
||||||
|
remote_reads_per_sec: 0,
|
||||||
|
running_since: "",
|
||||||
|
stats_watcher_listeners: 0,
|
||||||
|
stats_watcher_threads: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStats(order) {
|
||||||
|
lastOrder = order
|
||||||
|
|
||||||
|
fetch(window.api_endpoint + "/status").then(
|
||||||
|
resp => resp.json()
|
||||||
|
).then(resp => {
|
||||||
|
// Sort all queries by the selected sort column
|
||||||
|
resp.query_statistics.sort((a, b) => {
|
||||||
|
if (typeof (a[order]) === "number") {
|
||||||
|
// Sort ints from high to low
|
||||||
|
return b[order] - a[order]
|
||||||
|
} else {
|
||||||
|
// Sort strings alphabetically
|
||||||
|
return a[order].localeCompare(b[order])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort individual query callers by frequency
|
||||||
|
resp.query_statistics.forEach((v) => {
|
||||||
|
v.callers.sort((a, b) => b.count - a.count)
|
||||||
|
})
|
||||||
|
|
||||||
|
status = resp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let statsInterval = null
|
||||||
|
onMount(() => {
|
||||||
|
loadGraph(10080, 10, false);
|
||||||
|
getStats("calls")
|
||||||
|
statsInterval = setInterval(() => {
|
||||||
|
getStats(lastOrder)
|
||||||
|
}, 10000)
|
||||||
|
})
|
||||||
|
onDestroy(() => {
|
||||||
|
if (graphTimeout !== null) {
|
||||||
|
clearTimeout(graphTimeout)
|
||||||
|
}
|
||||||
|
if (statsInterval !== null) {
|
||||||
|
clearInterval(statsInterval)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="limit_width">
|
||||||
|
<h3>Bandwidth and views</h3>
|
||||||
|
</div>
|
||||||
|
<div class="highlight_dark" style="margin-bottom: 6px;">
|
||||||
|
<button on:click={() => { loadGraph(1440, 1, true) }}>Day</button>
|
||||||
|
<button on:click={() => { loadGraph(10080, 10, false) }}>Week</button>
|
||||||
|
<button on:click={() => { loadGraph(20160, 60, false) }}>Two Weeks</button>
|
||||||
|
<button on:click={() => { loadGraph(43200, 60, false) }}>Month</button>
|
||||||
|
<button on:click={() => { loadGraph(131400, 1440, false) }}>Quarter</button>
|
||||||
|
<button on:click={() => { loadGraph(262800, 1440, false) }}>Half-year</button>
|
||||||
|
<button on:click={() => { loadGraph(525600, 1440, false) }}>Year</button>
|
||||||
|
<button on:click={() => { loadGraph(1051200, 1440, false) }}>Two Years</button>
|
||||||
|
</div>
|
||||||
|
<Chart bind:this={graphBandwidth} dataType="bytes" label="Bandwidth" />
|
||||||
|
<hr/>
|
||||||
|
<Chart bind:this={graphViews} dataType="number" label="Views" />
|
||||||
|
<div class="highlight_dark">
|
||||||
|
Total usage from {start_time} to {end_time}<br/>
|
||||||
|
{formatDataVolume(total_bandwidth, 3)} bandwidth and {formatThousands(total_views, 3)} views
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<a class="button" href="/api/admin/call_stack">Call stack</a>
|
||||||
|
<a class="button" href="/api/admin/heap_profile">Heap profile</a>
|
||||||
|
<a class="button" href="/api/admin/cpu_profile">CPU profile (wait 1 min)</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="limit_width">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>DB Time</td>
|
||||||
|
<td>{formatDate(new Date(status.db_time), true, true, true)}</td>
|
||||||
|
<td>DB Latency</td>
|
||||||
|
<td>{formatNumber(status.db_latency / 1000, 3)} ms</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<h3>Pixelstore peers</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Address</td>
|
||||||
|
<td>Pos</td>
|
||||||
|
<td>Alive</td>
|
||||||
|
<td>Err</td>
|
||||||
|
<td>1m</td>
|
||||||
|
<td>5m</td>
|
||||||
|
<td>15m</td>
|
||||||
|
<td>Ping</td>
|
||||||
|
<td>Free</td>
|
||||||
|
<td>Min free</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each status.peers as peer}
|
||||||
|
<tr class="peer_row"
|
||||||
|
class:highlight_red={peer.free_space < peer.min_free_space / 2 || !peer.reachable}
|
||||||
|
class:highlight_blue={peer.free_space < peer.min_free_space}
|
||||||
|
class:highlight_green={peer.reachable}
|
||||||
|
>
|
||||||
|
<td>{peer.address}</td>
|
||||||
|
<td>{peer.position}</td>
|
||||||
|
<td>{peer.reachable}</td>
|
||||||
|
<td>{peer.unreachable_count}</td>
|
||||||
|
<td>{peer.load_1_min}</td>
|
||||||
|
<td>{peer.load_5_min}</td>
|
||||||
|
<td>{peer.load_15_min}</td>
|
||||||
|
<td>{formatDuration(peer.latency)}</td>
|
||||||
|
<td>{formatDataVolume(peer.free_space, 3)}</td>
|
||||||
|
<td>{formatDataVolume(peer.min_free_space, 3)}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Pixelstore stats</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Local reads</td>
|
||||||
|
<td>Local read size</td>
|
||||||
|
<td>Remote reads</td>
|
||||||
|
<td>Remote read size</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{status.local_reads}</td>
|
||||||
|
<td>{formatDataVolume(status.local_read_size, 4)}</td>
|
||||||
|
<td>{status.remote_reads}</td>
|
||||||
|
<td>{formatDataVolume(status.remote_read_size, 4)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{status.local_reads_per_sec.toPrecision(4)} / s</td>
|
||||||
|
<td>{formatDataVolume(status.local_read_size_per_sec, 4)} / s</td>
|
||||||
|
<td>{status.remote_reads_per_sec.toPrecision(4)} / s</td>
|
||||||
|
<td>{formatDataVolume(status.remote_read_size_per_sec, 4)} /s</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Websocket statistics</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Watcher</td>
|
||||||
|
<td>Threads</td>
|
||||||
|
<td>Listeners</td>
|
||||||
|
<td>Avg</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>File statistics</td>
|
||||||
|
<td>{status.stats_watcher_threads}</td>
|
||||||
|
<td>{status.stats_watcher_listeners}</td>
|
||||||
|
<td>{(status.stats_watcher_listeners / status.stats_watcher_threads).toPrecision(3)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3>Query statistics</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td style="cursor: pointer;" on:click={() => { getStats('query_name') }}>Query</td>
|
||||||
|
<td style="cursor: pointer;" on:click={() => { getStats('calls') }}>Calls</td>
|
||||||
|
<td style="cursor: pointer;" on:click={() => { getStats('average_duration') }}>Average Duration</td>
|
||||||
|
<td style="cursor: pointer;" on:click={() => { getStats('total_duration') }}>Total Duration</td>
|
||||||
|
<td>Callers</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tstat_body">
|
||||||
|
{#each status.query_statistics as q}
|
||||||
|
<tr>
|
||||||
|
<td>{q.query_name}</td>
|
||||||
|
<td>{q.calls}</td>
|
||||||
|
<td>{formatDuration(q.average_duration)}</td>
|
||||||
|
<td>{formatDuration(q.total_duration)}</td>
|
||||||
|
<td>
|
||||||
|
{#each q.callers as caller}
|
||||||
|
{caller.count}x {caller.name}<br/>
|
||||||
|
{/each}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.peer_row {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -86,9 +86,6 @@ onMount(get_bans);
|
|||||||
|
|
||||||
<div class="limit_width">
|
<div class="limit_width">
|
||||||
<div class="toolbar" style="text-align: left;">
|
<div class="toolbar" style="text-align: left;">
|
||||||
<a class="button" href="/admin">
|
|
||||||
<i class="icon">arrow_back</i> Return to admin panel
|
|
||||||
</a>
|
|
||||||
<div class="toolbar_spacer"></div>
|
<div class="toolbar_spacer"></div>
|
||||||
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||||
<i class="icon">create</i> Add IP ban
|
<i class="icon">create</i> Add IP ban
|
||||||
@@ -145,14 +142,16 @@ onMount(get_bans);
|
|||||||
<td>Reason</td>
|
<td>Reason</td>
|
||||||
<td>Ban time</td>
|
<td>Ban time</td>
|
||||||
<td>Expire time</td>
|
<td>Expire time</td>
|
||||||
|
<td>Offences</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{#each rows as row}
|
{#each rows as row}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{row.address}</td>
|
<td>{row.address}</td>
|
||||||
<td>{rows.reason}</td>
|
<td>{row.reason}</td>
|
||||||
<td>{formatDate(row.ban_time, true, true, false)}</td>
|
<td>{formatDate(row.ban_time, true, true, false)}</td>
|
||||||
<td>{formatDate(row.expire_time, true, true, false)}</td>
|
<td>{formatDate(row.expire_time, true, true, false)}</td>
|
||||||
|
<td>{row.offences}</td>
|
||||||
<td>
|
<td>
|
||||||
<button on:click|preventDefault={() => {delete_ban(row.address)}} class="button button_red">
|
<button on:click|preventDefault={() => {delete_ban(row.address)}} class="button button_red">
|
||||||
<i class="icon">delete</i>
|
<i class="icon">delete</i>
|
||||||
@@ -170,6 +169,7 @@ onMount(get_bans);
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
96
svelte/src/util/Chart.svelte
Normal file
96
svelte/src/util/Chart.svelte
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { formatDataVolume, formatNumber } from "./Formatting.svelte";
|
||||||
|
import { Chart } from "chart.js"
|
||||||
|
|
||||||
|
let chart_element
|
||||||
|
let chart_object
|
||||||
|
export let label = "label"
|
||||||
|
export let dataType = ""
|
||||||
|
|
||||||
|
export const chart = () => {
|
||||||
|
return chart_object
|
||||||
|
}
|
||||||
|
export const update = () => {
|
||||||
|
return chart_object.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.defaults.global.defaultFontColor = "#b3b3b3";
|
||||||
|
Chart.defaults.global.defaultFontSize = 15;
|
||||||
|
Chart.defaults.global.defaultFontFamily = "system-ui, sans-serif";
|
||||||
|
Chart.defaults.global.maintainAspectRatio = false;
|
||||||
|
Chart.defaults.global.elements.point.radius = 0;
|
||||||
|
Chart.defaults.global.tooltips.mode = "index";
|
||||||
|
Chart.defaults.global.tooltips.axis = "x";
|
||||||
|
Chart.defaults.global.tooltips.intersect = false;
|
||||||
|
Chart.defaults.global.animation.duration = 500;
|
||||||
|
Chart.defaults.global.animation.easing = "linear";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
chart_object = new Chart(
|
||||||
|
chart_element.getContext("2d"),
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: label,
|
||||||
|
backgroundColor: window.highlight_color,
|
||||||
|
borderWidth: 0,
|
||||||
|
lineTension: 0,
|
||||||
|
fill: true,
|
||||||
|
yAxisID: "ax_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend: { display: false },
|
||||||
|
scales: {
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
type: "linear",
|
||||||
|
display: true,
|
||||||
|
position: "left",
|
||||||
|
id: "ax_1",
|
||||||
|
ticks: {
|
||||||
|
callback: function (value, index, values) {
|
||||||
|
if (dataType == "bytes") {
|
||||||
|
return formatDataVolume(value, 3);
|
||||||
|
}
|
||||||
|
return formatNumber(value, 3);
|
||||||
|
},
|
||||||
|
beginAtZero: true
|
||||||
|
},
|
||||||
|
gridLines: { display: true },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
sampleSize: 1,
|
||||||
|
padding: 4,
|
||||||
|
minRotation: 0,
|
||||||
|
maxRotation: 0
|
||||||
|
},
|
||||||
|
gridLines: { display: false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas bind:this={chart_element}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -65,10 +65,10 @@ func adType() (banner, floater int) {
|
|||||||
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))
|
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch i := rand.Intn(4); i {
|
switch i := rand.Intn(3); i {
|
||||||
case 0, 1, 2: // 75%
|
case 0, 1: // 66%
|
||||||
floater = propellerFloat
|
floater = propellerFloat
|
||||||
case 3: // 25%
|
case 2: // 33%
|
||||||
floater = adSterraFloat
|
floater = adSterraFloat
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))
|
panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))
|
||||||
|
@@ -181,14 +181,14 @@ func New(
|
|||||||
{PST, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
{PST, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, handlerOpts{Auth: true})},
|
||||||
|
|
||||||
// Admin settings
|
// Admin settings
|
||||||
{GET, "admin" /* */, wc.serveTemplate("admin_panel", handlerOpts{Auth: true})},
|
{GET, "admin" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
|
||||||
|
{GET, "admin/abuse_reporters" /**/, wc.serveTemplate("admin", handlerOpts{Auth: true})},
|
||||||
|
{GET, "admin/abuse_reports" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
|
||||||
|
{GET, "admin/ip_bans" /* */, wc.serveTemplate("admin", handlerOpts{Auth: true})},
|
||||||
{GET, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},
|
{GET, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},
|
||||||
{PST, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},
|
{PST, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, handlerOpts{Auth: true})},
|
||||||
{GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, handlerOpts{Auth: true})},
|
{GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, handlerOpts{Auth: true})},
|
||||||
{PST, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, handlerOpts{Auth: true})},
|
{PST, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, handlerOpts{Auth: true})},
|
||||||
{GET, "admin/abuse_reporters" /**/, wc.serveTemplate("admin_abuse_reporters", handlerOpts{Auth: true})},
|
|
||||||
{GET, "admin/abuse_reports" /* */, wc.serveTemplate("admin_abuse_reports", handlerOpts{Auth: true})},
|
|
||||||
{GET, "admin/ip_bans" /* */, wc.serveTemplate("admin_ip_bans", handlerOpts{Auth: true})},
|
|
||||||
|
|
||||||
// Advertising related
|
// Advertising related
|
||||||
{GET, "click/:id" /* */, wc.serveAdClick},
|
{GET, "click/:id" /* */, wc.serveAdClick},
|
||||||
|
Reference in New Issue
Block a user