Add admin file details page for debugging file entries
This commit is contained in:
13
svelte/package-lock.json
generated
13
svelte/package-lock.json
generated
@@ -28,7 +28,6 @@
|
|||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/jsmediatags": "^3.9.6",
|
"@types/jsmediatags": "^3.9.6",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^24.10.0",
|
||||||
"baseline-browser-mapping": "^2.10.13",
|
|
||||||
"rollup": "^4.24.4",
|
"rollup": "^4.24.4",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
"rollup-plugin-svelte": "^7.2.2",
|
"rollup-plugin-svelte": "^7.2.2",
|
||||||
@@ -2708,9 +2707,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.13",
|
"version": "2.11.12",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz",
|
||||||
"integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
|
"integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -2804,9 +2803,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001749",
|
"version": "1.0.30001806",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
|
||||||
"integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
|
"integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/jsmediatags": "^3.9.6",
|
"@types/jsmediatags": "^3.9.6",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^24.10.0",
|
||||||
"baseline-browser-mapping": "^2.10.13",
|
|
||||||
"rollup": "^4.24.4",
|
"rollup": "^4.24.4",
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
"rollup-plugin-svelte": "^7.2.2",
|
"rollup-plugin-svelte": "^7.2.2",
|
||||||
|
|||||||
180
svelte/src/admin_panel/FileInfo.svelte
Normal file
180
svelte/src/admin_panel/FileInfo.svelte
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { formatDataVolume, formatDate, formatNumber } from "util/Formatting"
|
||||||
|
import { get_endpoint, check_response } from "lib/NovaAPI"
|
||||||
|
import { get_status, type Peer } from "lib/AdminAPI"
|
||||||
|
|
||||||
|
let hash = $state("")
|
||||||
|
let file = $state(null)
|
||||||
|
let error = $state("")
|
||||||
|
let loading = $state(false)
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
const h = hash.trim().replace(/^0x/i, "")
|
||||||
|
file = null
|
||||||
|
error = ""
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
file = await check_response(
|
||||||
|
await fetch(get_endpoint() + "/admin/file/" + h + "/info")
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
error = err.message || JSON.stringify(err)
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server UUID to peer, so shard locations can be shown as hostnames. A
|
||||||
|
// decommissioned server is still in the shard map but no longer in the peer
|
||||||
|
// list, so the UUID has to remain the fallback
|
||||||
|
let peers: { [id: string]: Peer } = $state({})
|
||||||
|
onMount(async () => {
|
||||||
|
const status = await get_status()
|
||||||
|
peers = Object.fromEntries(status.peers.map(p => [p.id, p]))
|
||||||
|
})
|
||||||
|
const server = (id: string) => {
|
||||||
|
const peer = peers[id]
|
||||||
|
return peer ? peer.hostname : id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go marshals a missing timestamp as the zero time instead of null
|
||||||
|
const date = (d: string) => (!d || d.startsWith("0001-")) ? "never" : formatDate(d)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>File inspector</h2>
|
||||||
|
<form onsubmit={e => { e.preventDefault(); load() }}>
|
||||||
|
<label for="hash_input">File hash (hex sha256)</label>
|
||||||
|
<input id="hash_input" bind:value={hash} onclick={e => e.currentTarget.select()}/>
|
||||||
|
<button type="submit" disabled={loading}>
|
||||||
|
<i class="icon">search</i> Look up
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<p class="highlight_red">{error}</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{#if file}
|
||||||
|
<section>
|
||||||
|
<h3>File row</h3>
|
||||||
|
<div class="table_scroll">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Hash</td><td>{file.hash}</td></tr>
|
||||||
|
<tr><td>Hash UUID</td><td>{file.hash_uuid}</td></tr>
|
||||||
|
<tr><td>SHA1</td><td>{file.hash_sha1}</td></tr>
|
||||||
|
<tr><td>MD5</td><td>{file.hash_md5}</td></tr>
|
||||||
|
<tr><td>Size</td><td>{formatDataVolume(file.size, 4)} ({file.size} B)</td></tr>
|
||||||
|
<tr><td>Created</td><td>{date(file.created)}</td></tr>
|
||||||
|
<tr><td>Thumbnail</td><td>{file.thumbnail_status || "none"}</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>Abuse</td>
|
||||||
|
<td>
|
||||||
|
{#if file.abuse_type}
|
||||||
|
{file.abuse_type}, reported by {file.abuse_reporter_name}
|
||||||
|
<{file.abuse_reporter_email}>
|
||||||
|
{:else}
|
||||||
|
none
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>Last referenced</td><td>{date(file.last_referenced)}</td></tr>
|
||||||
|
<tr><td>Last dereferenced</td><td>{date(file.last_dereferenced)}</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>Collectable after</td>
|
||||||
|
<td>
|
||||||
|
{date(file.collectable_after)}
|
||||||
|
{#if file.live_references > 0}
|
||||||
|
(blocked by {file.live_references} live references)
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cache servers</td>
|
||||||
|
<td>{file.cache_servers?.map(server).join(", ") || "none"}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Reed-Solomon</td>
|
||||||
|
<td>
|
||||||
|
{file.data_shards} data + {file.parity_shards} parity,
|
||||||
|
{formatDataVolume(file.shard_size, 4)} per shard,
|
||||||
|
{formatDataVolume(file.shard_block_size, 4)} blocks
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Redundancy</td>
|
||||||
|
<td>
|
||||||
|
{formatNumber(file.redundancy, 3)}x
|
||||||
|
{#if file.missing_shards?.length}
|
||||||
|
<span class="highlight_red">
|
||||||
|
missing shards: {file.missing_shards.join(", ")}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Storage locations ({file.shards.length} shards)</h3>
|
||||||
|
<div class="table_scroll">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><td>Index</td><td>Shard hash</td><td>Server</td></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each file.shards as shard}
|
||||||
|
<tr>
|
||||||
|
<td>{shard.index}</td>
|
||||||
|
<td>{shard.hash}</td>
|
||||||
|
<td title={shard.server_id}>{server(shard.server_id)}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
References ({file.references.length}{file.reference_limit ? "+" : ""},
|
||||||
|
{file.live_references} live)
|
||||||
|
</h3>
|
||||||
|
<div class="table_scroll">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>User</td>
|
||||||
|
<td>Size</td>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>Views</td>
|
||||||
|
<td>Created</td>
|
||||||
|
<td>Modified</td>
|
||||||
|
<td>Deleted</td>
|
||||||
|
<td>Abuse</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each file.references as ref}
|
||||||
|
<tr>
|
||||||
|
<td><a href="/d/{ref.id}">{ref.id}</a></td>
|
||||||
|
<td>{ref.name}</td>
|
||||||
|
<td>{ref.user_id}</td>
|
||||||
|
<td>{formatDataVolume(ref.size, 3)}</td>
|
||||||
|
<td>{ref.type}</td>
|
||||||
|
<td>{ref.views}</td>
|
||||||
|
<td>{date(ref.created)}</td>
|
||||||
|
<td>{date(ref.modified)}</td>
|
||||||
|
<td>{ref.deleted ? formatDate(ref.deleted) : ""}</td>
|
||||||
|
<td>{ref.abuse_type}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
@@ -6,6 +6,7 @@ import { color_by_name } from "util/Util";
|
|||||||
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
||||||
import PeerTable from "./PeerTable.svelte";
|
import PeerTable from "./PeerTable.svelte";
|
||||||
import { get_endpoint } from "lib/NovaAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
import { get_status, type ServerStatus } from "lib/AdminAPI";
|
||||||
|
|
||||||
let graphEgress: Chart = $state()
|
let graphEgress: Chart = $state()
|
||||||
let graphDownloads: Chart = $state()
|
let graphDownloads: Chart = $state()
|
||||||
@@ -60,34 +61,32 @@ const loadGraph = (minutes: number, interval: number, live: boolean) => {
|
|||||||
// Load performance statistics
|
// Load performance statistics
|
||||||
|
|
||||||
let lastOrder: string = $state();
|
let lastOrder: string = $state();
|
||||||
let status = $state({
|
let status: ServerStatus = $state({
|
||||||
|
running_since: "",
|
||||||
pid: 0,
|
pid: 0,
|
||||||
cpu_profile_running_since: "",
|
cpu_profile_running_since: "",
|
||||||
db_latency: 0,
|
db_latency: 0,
|
||||||
db_time: "",
|
db_time: "",
|
||||||
|
query_statistics: [],
|
||||||
|
filesystem_watcher_threads: 0,
|
||||||
|
filesystem_watcher_listeners: 0,
|
||||||
|
peers: [],
|
||||||
cache_threshold: 0,
|
cache_threshold: 0,
|
||||||
local_read_size: 0,
|
|
||||||
local_read_size_per_sec: 0,
|
|
||||||
local_reads: 0,
|
local_reads: 0,
|
||||||
local_reads_per_sec: 0,
|
local_reads_per_sec: 0,
|
||||||
|
local_read_size: 0,
|
||||||
|
local_read_size_per_sec: 0,
|
||||||
local_readers: 0,
|
local_readers: 0,
|
||||||
neighbour_read_size: 0,
|
|
||||||
neighbour_read_size_per_sec: 0,
|
|
||||||
neighbour_reads: 0,
|
neighbour_reads: 0,
|
||||||
neighbour_reads_per_sec: 0,
|
neighbour_reads_per_sec: 0,
|
||||||
|
neighbour_read_size: 0,
|
||||||
|
neighbour_read_size_per_sec: 0,
|
||||||
neighbour_readers: 0,
|
neighbour_readers: 0,
|
||||||
remote_read_size: 0,
|
|
||||||
remote_read_size_per_sec: 0,
|
|
||||||
remote_reads: 0,
|
remote_reads: 0,
|
||||||
remote_reads_per_sec: 0,
|
remote_reads_per_sec: 0,
|
||||||
|
remote_read_size: 0,
|
||||||
|
remote_read_size_per_sec: 0,
|
||||||
remote_readers: 0,
|
remote_readers: 0,
|
||||||
peers: [],
|
|
||||||
query_statistics: [],
|
|
||||||
running_since: "",
|
|
||||||
stats_watcher_listeners: 0,
|
|
||||||
stats_watcher_threads: 0,
|
|
||||||
filesystem_watcher_listeners: 0,
|
|
||||||
filesystem_watcher_threads: 0,
|
|
||||||
})
|
})
|
||||||
let total_reads = $derived(status.local_reads + status.neighbour_reads + status.remote_reads)
|
let total_reads = $derived(status.local_reads + status.neighbour_reads + status.remote_reads)
|
||||||
let total_read_size = $derived(status.local_read_size + status.neighbour_read_size + status.remote_read_size)
|
let total_read_size = $derived(status.local_read_size + status.neighbour_read_size + status.remote_read_size)
|
||||||
@@ -95,9 +94,7 @@ let total_read_size = $derived(status.local_read_size + status.neighbour_read_si
|
|||||||
function getStats(order: string) {
|
function getStats(order: string) {
|
||||||
lastOrder = order
|
lastOrder = order
|
||||||
|
|
||||||
fetch(get_endpoint() + "/status").then(
|
get_status().then(resp => {
|
||||||
resp => resp.json()
|
|
||||||
).then(resp => {
|
|
||||||
// Sort all queries by the selected sort column
|
// Sort all queries by the selected sort column
|
||||||
resp.query_statistics.sort((a, b) => {
|
resp.query_statistics.sort((a, b) => {
|
||||||
if (typeof (a[order]) === "number") {
|
if (typeof (a[order]) === "number") {
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ const groups: {
|
|||||||
{metric: "pixelstore_reed_solomon_read_size", data_type: "bytes"},
|
{metric: "pixelstore_reed_solomon_read_size", data_type: "bytes"},
|
||||||
{metric: "pixelstore_read_retry_success", data_type: "number"},
|
{metric: "pixelstore_read_retry_success", data_type: "number"},
|
||||||
{metric: "pixelstore_read_retry_error", data_type: "number"},
|
{metric: "pixelstore_read_retry_error", data_type: "number"},
|
||||||
|
{metric: "pixelstore_remote_read", data_type: "number"},
|
||||||
|
{metric: "pixelstore_remote_read_ttfb", data_type: "duration"},
|
||||||
|
{
|
||||||
|
metric: "pixelstore_remote_read_ttfb_avg",
|
||||||
|
agg_base: "pixelstore_remote_read_ttfb",
|
||||||
|
agg_divisor: "pixelstore_remote_read",
|
||||||
|
data_type: "duration",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
title: "Pixelstore shards", expanded: false,
|
title: "Pixelstore shards", expanded: false,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { get_status } from "lib/AdminAPI";
|
||||||
|
import { loading_run } from "lib/Loading";
|
||||||
import { check_response, get_endpoint } from "lib/NovaAPI";
|
import { check_response, get_endpoint } from "lib/NovaAPI";
|
||||||
import hsl2rgb from "pure-color/convert/hsl2rgb";
|
import hsl2rgb from "pure-color/convert/hsl2rgb";
|
||||||
import rgb2hex from "pure-color/convert/rgb2hex";
|
import rgb2hex from "pure-color/convert/rgb2hex";
|
||||||
@@ -61,15 +62,10 @@ export const host_label = async (id: string): Promise<string> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const load_host_names = async () => {
|
export const load_host_names = async () => {
|
||||||
loading_start()
|
await loading_run(async () => {
|
||||||
try {
|
const status = await get_status()
|
||||||
const req = await fetch(get_endpoint() + "/status")
|
for (const peer of status.peers) {
|
||||||
const j = await req.json()
|
|
||||||
|
|
||||||
for (const peer of j.peers) {
|
|
||||||
host_names[peer.id] = peer.hostname
|
host_names[peer.id] = peer.hostname
|
||||||
}
|
}
|
||||||
} finally {
|
})
|
||||||
loading_finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,37 +3,17 @@ import { flip } from "svelte/animate";
|
|||||||
import { formatDataVolume } from "util/Formatting";
|
import { formatDataVolume } from "util/Formatting";
|
||||||
import SortButton from "layout/SortButton.svelte";
|
import SortButton from "layout/SortButton.svelte";
|
||||||
import { loading_run } from "lib/Loading";
|
import { loading_run } from "lib/Loading";
|
||||||
import { admin_decommission_node } from "lib/AdminAPI";
|
import { admin_decommission_node, type Peer } from "lib/AdminAPI";
|
||||||
|
|
||||||
let {
|
// The computed columns are not part of the API response, they are filled in
|
||||||
peers = $bindable([])
|
// by the effect below
|
||||||
}: {
|
type TablePeer = Peer & {
|
||||||
peers: {
|
avg_network_total?: number
|
||||||
id: string
|
usage_percent?: number
|
||||||
ip: string
|
network_ratio?: number
|
||||||
port: number
|
}
|
||||||
hostname: string
|
|
||||||
role: string
|
|
||||||
reachable: boolean
|
|
||||||
unreachable_count: number
|
|
||||||
latency: number
|
|
||||||
last_seen: string
|
|
||||||
free_space: number
|
|
||||||
min_free_space: number
|
|
||||||
load_1_min: number
|
|
||||||
load_5_min: number
|
|
||||||
load_15_min: number
|
|
||||||
avg_network_tx: number
|
|
||||||
avg_network_rx: number
|
|
||||||
port_speed: number
|
|
||||||
cache_threshold: number
|
|
||||||
|
|
||||||
// Our props
|
let { peers = $bindable([]) }: { peers: TablePeer[] } = $props();
|
||||||
avg_network_total?: number
|
|
||||||
usage_percent?: number
|
|
||||||
network_ratio?: number
|
|
||||||
}[]
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
// If not all peers are online we show the decommission button
|
// If not all peers are online we show the decommission button
|
||||||
let all_reachable = $derived(peers.reduce((acc, val) => {
|
let all_reachable = $derived(peers.reduce((acc, val) => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import PayPalTaxes from "./PayPalTaxes.svelte";
|
|||||||
import UserBans from "./user_bans/UserBans.svelte";
|
import UserBans from "./user_bans/UserBans.svelte";
|
||||||
import InvoiceVat from "./InvoiceVAT.svelte";
|
import InvoiceVat from "./InvoiceVAT.svelte";
|
||||||
import HostMetrics from "./HostMetrics.svelte";
|
import HostMetrics from "./HostMetrics.svelte";
|
||||||
|
import FileInfo from "./FileInfo.svelte";
|
||||||
|
|
||||||
let pages = [
|
let pages = [
|
||||||
{
|
{
|
||||||
@@ -53,6 +54,11 @@ let pages = [
|
|||||||
title: "Host Metrics",
|
title: "Host Metrics",
|
||||||
icon: "area_chart",
|
icon: "area_chart",
|
||||||
component: HostMetrics,
|
component: HostMetrics,
|
||||||
|
}, {
|
||||||
|
path: "/admin/file_info",
|
||||||
|
title: "File Inspector",
|
||||||
|
icon: "search",
|
||||||
|
component: FileInfo,
|
||||||
}, {
|
}, {
|
||||||
path: "/admin/mollie_settlements",
|
path: "/admin/mollie_settlements",
|
||||||
title: "Prepaid accounting",
|
title: "Prepaid accounting",
|
||||||
|
|||||||
@@ -39,3 +39,66 @@ export const admin_decommission_node = async (peer_id: string) => {
|
|||||||
)
|
)
|
||||||
) as GenericResponse
|
) as GenericResponse
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Peer = {
|
||||||
|
id: string
|
||||||
|
ip: string
|
||||||
|
port: number
|
||||||
|
hostname: string
|
||||||
|
role: string
|
||||||
|
reachable: boolean
|
||||||
|
unreachable_count: number
|
||||||
|
latency: number
|
||||||
|
last_seen: string
|
||||||
|
free_space: number
|
||||||
|
min_free_space: number
|
||||||
|
load_1_min: number
|
||||||
|
load_5_min: number
|
||||||
|
load_15_min: number
|
||||||
|
avg_network_tx: number
|
||||||
|
avg_network_rx: number
|
||||||
|
port_speed: number
|
||||||
|
cache_threshold: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryStats = {
|
||||||
|
query_name: string
|
||||||
|
calls: number
|
||||||
|
total_duration: number
|
||||||
|
average_duration: number
|
||||||
|
callers: { name: string, count: number }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServerStatus = {
|
||||||
|
running_since: string
|
||||||
|
pid: number
|
||||||
|
cpu_profile_running_since: string
|
||||||
|
db_latency: number
|
||||||
|
db_time: string
|
||||||
|
query_statistics: QueryStats[]
|
||||||
|
filesystem_watcher_threads: number
|
||||||
|
filesystem_watcher_listeners: number
|
||||||
|
peers: Peer[]
|
||||||
|
cache_threshold: number
|
||||||
|
local_reads: number
|
||||||
|
local_reads_per_sec: number
|
||||||
|
local_read_size: number
|
||||||
|
local_read_size_per_sec: number
|
||||||
|
local_readers: number
|
||||||
|
neighbour_reads: number
|
||||||
|
neighbour_reads_per_sec: number
|
||||||
|
neighbour_read_size: number
|
||||||
|
neighbour_read_size_per_sec: number
|
||||||
|
neighbour_readers: number
|
||||||
|
remote_reads: number
|
||||||
|
remote_reads_per_sec: number
|
||||||
|
remote_read_size: number
|
||||||
|
remote_read_size_per_sec: number
|
||||||
|
remote_readers: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const get_status = async () => {
|
||||||
|
return await check_response(
|
||||||
|
await fetch(get_endpoint() + "/status")
|
||||||
|
) as ServerStatus
|
||||||
|
};
|
||||||
|
|||||||
@@ -72,8 +72,13 @@ export const formatDuration = (ms: number, decimals: number) => {
|
|||||||
res += Math.floor((ms % hour) / minute) + "m "
|
res += Math.floor((ms % hour) / minute) + "m "
|
||||||
remainingDecimals--
|
remainingDecimals--
|
||||||
}
|
}
|
||||||
|
if (ms >= second && remainingDecimals > 0) {
|
||||||
|
res += Math.floor((ms % minute) / second) + "s "
|
||||||
|
remainingDecimals--
|
||||||
|
}
|
||||||
if (remainingDecimals > 0) {
|
if (remainingDecimals > 0) {
|
||||||
res += ((ms % minute) / second).toFixed(remainingDecimals) + "s"
|
res += (ms % second) + "ms "
|
||||||
|
remainingDecimals--
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2017",
|
"es2019",
|
||||||
"dom"
|
"dom"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"target": "ES2017",
|
"target": "ES2019",
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"module": "preserve",
|
"module": "preserve",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
@@ -548,6 +548,7 @@ footer, .footer_content,
|
|||||||
.upload_task,
|
.upload_task,
|
||||||
.add_button,
|
.add_button,
|
||||||
.expandable,
|
.expandable,
|
||||||
|
.bubble,
|
||||||
.upload_widget {
|
.upload_widget {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
@@ -596,7 +597,6 @@ code {
|
|||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file_preview,
|
|
||||||
hr,
|
hr,
|
||||||
.toolbar > .separator,
|
.toolbar > .separator,
|
||||||
.toolbar > .grid > .separator,
|
.toolbar > .grid > .separator,
|
||||||
@@ -676,6 +676,7 @@ footer, .footer_content,
|
|||||||
.upload_task,
|
.upload_task,
|
||||||
.add_button,
|
.add_button,
|
||||||
.expandable,
|
.expandable,
|
||||||
|
.bubble,
|
||||||
.upload_widget,
|
.upload_widget,
|
||||||
.highlight_shaded {
|
.highlight_shaded {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
@@ -721,7 +722,6 @@ code {
|
|||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file_preview,
|
|
||||||
hr,
|
hr,
|
||||||
.toolbar > .separator,
|
.toolbar > .separator,
|
||||||
.toolbar > .grid > .separator {
|
.toolbar > .grid > .separator {
|
||||||
|
|||||||
Reference in New Issue
Block a user