Update to svelte 5
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { formatDate, formatNumber } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let report
|
||||
export let ip_report_count
|
||||
let preview = false
|
||||
let { report, ip_report_count } = $props();
|
||||
let preview = $state(false)
|
||||
|
||||
$: can_grant = report.status !== "granted"
|
||||
$: can_reject = report.status !== "rejected"
|
||||
let can_grant = $derived(report.status !== "granted")
|
||||
let can_reject = $derived(report.status !== "rejected")
|
||||
|
||||
let set_status = async (action, report_type) => {
|
||||
dispatch("resolve_report", {action: action, report_type: report_type})
|
||||
@@ -17,57 +16,59 @@ let set_status = async (action, report_type) => {
|
||||
</script>
|
||||
|
||||
<Expandable expanded={report.status === "pending"} click_expand>
|
||||
<div slot="header" class="header">
|
||||
<div class="icon_cell">
|
||||
<img class="file_icon" src={"/api/file/"+report.file.id+"/thumbnail"} alt="File thumbnail"/>
|
||||
</div>
|
||||
|
||||
<div class="title">
|
||||
{report.file.name}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Type<br/>
|
||||
{report.file.abuse_type === "" ? report.type : report.file.abuse_type}
|
||||
</div>
|
||||
{#if report.status !== "pending"}
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{report.status}
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="icon_cell">
|
||||
<img class="file_icon" src={"/api/file/"+report.file.id+"/thumbnail"} alt="File thumbnail"/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="stats">R<br/>{report.reports.length}</div>
|
||||
<div class="stats">V<br/>{formatNumber(report.file.views, 3)}</div>
|
||||
<div class="stats">DL<br/>{formatNumber(report.file.bandwidth_used / report.file.size, 3)}</div>
|
||||
</div>
|
||||
|
||||
<div class="title">
|
||||
{report.file.name}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Type<br/>
|
||||
{report.file.abuse_type === "" ? report.type : report.file.abuse_type}
|
||||
</div>
|
||||
{#if report.status !== "pending"}
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{report.status}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="stats">R<br/>{report.reports.length}</div>
|
||||
<div class="stats">V<br/>{formatNumber(report.file.views, 3)}</div>
|
||||
<div class="stats">DL<br/>{formatNumber(report.file.bandwidth_used / report.file.size, 3)}</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div class="details">
|
||||
<div class="toolbar">
|
||||
<div class="action_list">
|
||||
<a class="button" target="_blank" href={"/u/"+report.file.id} rel="noreferrer">
|
||||
<i class="icon">open_in_new</i> Open file
|
||||
</a>
|
||||
<button class:button_highlight={preview} on:click={() => {preview = !preview}}>
|
||||
<button class:button_highlight={preview} onclick={() => {preview = !preview}}>
|
||||
<i class="icon">visibility</i> Preview
|
||||
</button>
|
||||
{#if can_grant}
|
||||
<button class="button_highlight" on:click={() => {set_status("grant", report.type)}}>
|
||||
<button class="button_highlight" onclick={() => {set_status("grant", report.type)}}>
|
||||
<i class="icon">done</i> Block ({report.type})
|
||||
</button>
|
||||
{/if}
|
||||
{#if can_reject}
|
||||
<button class="button_red" on:click={() => {set_status("reject", "")}}>
|
||||
<button class="button_red" onclick={() => {set_status("reject", "")}}>
|
||||
<i class="icon">delete</i> Ignore
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="type_list">
|
||||
<button on:click={() => {set_status("grant", "copyright")}}>copyright</button>
|
||||
<button on:click={() => {set_status("grant", "terrorism")}}>terrorism</button>
|
||||
<button on:click={() => {set_status("grant", "gore")}}>gore</button>
|
||||
<button on:click={() => {set_status("grant", "child_abuse")}}>child_abuse</button>
|
||||
<button on:click={() => {set_status("grant", "zoophilia")}}>zoophilia</button>
|
||||
<button on:click={() => {set_status("grant", "malware")}}>malware</button>
|
||||
<button on:click={() => {set_status("grant", "doxing")}}>doxing</button>
|
||||
<button on:click={() => {set_status("grant", "revenge_porn")}}>revenge_porn</button>
|
||||
<button onclick={() => {set_status("grant", "copyright")}}>copyright</button>
|
||||
<button onclick={() => {set_status("grant", "terrorism")}}>terrorism</button>
|
||||
<button onclick={() => {set_status("grant", "gore")}}>gore</button>
|
||||
<button onclick={() => {set_status("grant", "child_abuse")}}>child_abuse</button>
|
||||
<button onclick={() => {set_status("grant", "zoophilia")}}>zoophilia</button>
|
||||
<button onclick={() => {set_status("grant", "malware")}}>malware</button>
|
||||
<button onclick={() => {set_status("grant", "doxing")}}>doxing</button>
|
||||
<button onclick={() => {set_status("grant", "revenge_porn")}}>revenge_porn</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
@@ -101,12 +102,12 @@ let set_status = async (action, report_type) => {
|
||||
<td>{ip_report_count[user_report.ip_address]}</td>
|
||||
<td>
|
||||
{#if can_grant}
|
||||
<button on:click={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "grant"})}>
|
||||
<button onclick={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "grant"})}>
|
||||
Accept all
|
||||
</button>
|
||||
{/if}
|
||||
{#if can_reject}
|
||||
<button on:click={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "reject"})}>
|
||||
<button onclick={() => dispatch("resolve_by_ip", {ip: user_report.ip_address, action: "reject"})}>
|
||||
Ignore all
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -4,12 +4,12 @@ import AbuseReport from "./AbuseReport.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let loading = true
|
||||
let reports = []
|
||||
let reports = $state([])
|
||||
|
||||
let startPicker
|
||||
let endPicker
|
||||
let startPicker = $state()
|
||||
let endPicker = $state()
|
||||
|
||||
let tab = "pending"
|
||||
let tab = $state("pending")
|
||||
|
||||
const get_reports = async () => {
|
||||
loading_start()
|
||||
@@ -74,7 +74,7 @@ const get_reports = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
let ip_report_count = {}
|
||||
let ip_report_count = $state({})
|
||||
const count_ip_reports = () => {
|
||||
ip_report_count = {}
|
||||
reports.forEach(v => {
|
||||
@@ -163,19 +163,19 @@ onMount(() => {
|
||||
<div>Range:</div>
|
||||
<input type="date" bind:this={startPicker}/>
|
||||
<input type="date" bind:this={endPicker}/>
|
||||
<button on:click={get_reports}>Go</button>
|
||||
<button onclick={get_reports}>Go</button>
|
||||
</div>
|
||||
|
||||
<div class="tab_bar">
|
||||
<button on:click={() => {tab = "pending"; get_reports()}} class:button_highlight={tab === "pending"}>
|
||||
<button onclick={() => {tab = "pending"; get_reports()}} class:button_highlight={tab === "pending"}>
|
||||
<i class="icon">flag</i>
|
||||
Pending
|
||||
</button>
|
||||
<button on:click={() => {tab = "granted"; get_reports()}} class:button_highlight={tab === "granted"}>
|
||||
<button onclick={() => {tab = "granted"; get_reports()}} class:button_highlight={tab === "granted"}>
|
||||
<i class="icon">flag</i>
|
||||
Granted
|
||||
</button>
|
||||
<button on:click={() => {tab = "rejected"; get_reports()}} class:button_highlight={tab === "rejected"}>
|
||||
<button onclick={() => {tab = "rejected"; get_reports()}} class:button_highlight={tab === "rejected"}>
|
||||
<i class="icon">flag</i>
|
||||
Rejected
|
||||
</button>
|
||||
|
||||
@@ -17,25 +17,25 @@ type Reporter = {
|
||||
last_message_html: string,
|
||||
}
|
||||
|
||||
let reporters: Reporter[] = []
|
||||
$: reporters_pending = reporters.reduce((acc, val) => {
|
||||
let reporters: Reporter[] = $state([])
|
||||
let reporters_pending = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "pending") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
$: reporters_trusted = reporters.reduce((acc, val) => {
|
||||
}, []))
|
||||
let reporters_trusted = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "trusted") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
$: reporters_rejected = reporters.reduce((acc, val) => {
|
||||
}, []))
|
||||
let reporters_rejected = $derived(reporters.reduce((acc, val) => {
|
||||
if (val.status === "rejected") {
|
||||
acc.push(val)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}, []))
|
||||
|
||||
const get_reporters = async () => {
|
||||
loading_start()
|
||||
@@ -52,13 +52,14 @@ const get_reporters = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
let edit_button: HTMLButtonElement
|
||||
let creating = false
|
||||
let new_reporter_from_address: HTMLInputElement
|
||||
let new_reporter_name: HTMLInputElement
|
||||
let new_reporter_status = "trusted"
|
||||
let edit_button: HTMLButtonElement = $state()
|
||||
let creating = $state(false)
|
||||
let new_reporter_from_address: HTMLInputElement = $state()
|
||||
let new_reporter_name: HTMLInputElement = $state()
|
||||
let new_reporter_status = $state("trusted")
|
||||
|
||||
const create_reporter = async () => {
|
||||
const create_reporter = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (!new_reporter_from_address.value) {
|
||||
alert("Please enter an e-mail address")
|
||||
return
|
||||
@@ -139,16 +140,16 @@ onMount(get_reporters);
|
||||
<section>
|
||||
<div class="toolbar" style="text-align: left;">
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={() => get_reporters()}>
|
||||
<button onclick={() => get_reporters()}>
|
||||
<i class="icon">refresh</i>
|
||||
</button>
|
||||
<button bind:this={edit_button} class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||
<button bind:this={edit_button} class:button_highlight={creating} onclick={() => {creating = !creating}}>
|
||||
<i class="icon">create</i> Add abuse reporter
|
||||
</button>
|
||||
</div>
|
||||
{#if creating}
|
||||
<div class="highlight_shaded">
|
||||
<form on:submit|preventDefault={create_reporter}>
|
||||
<form onsubmit={create_reporter}>
|
||||
<div class="form">
|
||||
<label for="field_from_address">E-mail address</label>
|
||||
<input id="field_from_address" type="text" bind:this={new_reporter_from_address}/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { run, preventDefault } from 'svelte/legacy';
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Modal from "util/Modal.svelte"
|
||||
@@ -7,13 +8,12 @@ import { flip } from "svelte/animate";
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let reporters = []
|
||||
let { reporters = $bindable([]) } = $props();
|
||||
|
||||
$: update_table(reporters)
|
||||
const update_table = (reporters) => sort("")
|
||||
|
||||
let sort_field = "last_used"
|
||||
let asc = false
|
||||
let sort_field = $state("last_used")
|
||||
let asc = $state(false)
|
||||
const sort = (field) => {
|
||||
if (field !== "" && field === sort_field) asc = !asc
|
||||
if (field === "") field = sort_field
|
||||
@@ -40,16 +40,19 @@ const sort = (field) => {
|
||||
reporters = reporters
|
||||
}
|
||||
|
||||
let modal
|
||||
let preview_subject = ""
|
||||
let preview_html = ""
|
||||
let preview_text = ""
|
||||
let modal: Modal = $state()
|
||||
let preview_subject = $state("")
|
||||
let preview_html = $state("")
|
||||
let preview_text = $state("")
|
||||
const toggle_preview = (rep) => {
|
||||
preview_subject = rep.last_message_subject
|
||||
preview_text = rep.last_message_text
|
||||
preview_html = rep.last_message_html
|
||||
modal.show()
|
||||
}
|
||||
run(() => {
|
||||
update_table(reporters)
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="table_scroll">
|
||||
@@ -75,23 +78,23 @@ const toggle_preview = (rep) => {
|
||||
<td>{formatDate(rep.last_used, true, true, false)}</td>
|
||||
<td>{formatDate(rep.created, false, false, false)}</td>
|
||||
<td>
|
||||
<button on:click|preventDefault={() => toggle_preview(rep)} class="button round">
|
||||
<button onclick={preventDefault(() => toggle_preview(rep))} class="button round">
|
||||
<i class="icon">email</i>
|
||||
</button>
|
||||
<button on:click|preventDefault={() => {dispatch("edit", rep)}} class="button round">
|
||||
<button onclick={preventDefault(() => {dispatch("edit", rep)})} class="button round">
|
||||
<i class="icon">edit</i>
|
||||
</button>
|
||||
{#if rep.status !== "trusted"}
|
||||
<button on:click|preventDefault={() => {dispatch("approve", rep)}} class="button button_highlight round">
|
||||
<button onclick={preventDefault(() => {dispatch("approve", rep)})} class="button button_highlight round">
|
||||
<i class="icon">check</i>
|
||||
</button>
|
||||
{/if}
|
||||
{#if rep.status !== "rejected"}
|
||||
<button on:click|preventDefault={() => {dispatch("spam", rep)}} class="button button_red round">
|
||||
<button onclick={preventDefault(() => {dispatch("spam", rep)})} class="button button_red round">
|
||||
<i class="icon">block</i>
|
||||
</button>
|
||||
{/if}
|
||||
<button on:click|preventDefault={() => {dispatch("delete", rep)}} class="button button_red round">
|
||||
<button onclick={preventDefault(() => {dispatch("delete", rep)})} class="button button_red round">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { formatDataVolume, formatThousands, formatDate, formatNumber, formatDuration } from "util/Formatting";
|
||||
import Chart from "util/Chart.svelte";
|
||||
import { color_by_name } from "util/Util.svelte";
|
||||
import { color_by_name } from "util/Util";
|
||||
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
||||
import PeerTable from "./PeerTable.svelte";
|
||||
|
||||
let graphViews
|
||||
let graphBandwidth
|
||||
let graphViews = $state()
|
||||
let graphBandwidth = $state()
|
||||
let graphTimeout = null
|
||||
|
||||
let start_time = ""
|
||||
let end_time = ""
|
||||
let total_bandwidth = 0
|
||||
let total_bandwidth_paid = 0
|
||||
let total_views = 0
|
||||
let total_downloads = 0
|
||||
let start_time = $state("")
|
||||
let end_time = $state("")
|
||||
let total_bandwidth = $state(0)
|
||||
let total_bandwidth_paid = $state(0)
|
||||
let total_views = $state(0)
|
||||
let total_downloads = $state(0)
|
||||
const loadGraph = (minutes, interval, live) => {
|
||||
if (graphTimeout !== null) { clearTimeout(graphTimeout) }
|
||||
if (live) {
|
||||
@@ -64,8 +64,8 @@ const loadGraph = (minutes, interval, live) => {
|
||||
|
||||
// Load performance statistics
|
||||
|
||||
let lastOrder;
|
||||
let status = {
|
||||
let lastOrder = $state();
|
||||
let status = $state({
|
||||
cpu_profile_running_since: "",
|
||||
db_latency: 0,
|
||||
db_time: "",
|
||||
@@ -93,9 +93,9 @@ let status = {
|
||||
rate_limit_watcher_listeners: 0,
|
||||
download_clients: 0,
|
||||
download_connections: 0,
|
||||
}
|
||||
$: total_reads = status.local_reads + status.neighbour_reads + status.remote_reads
|
||||
$: total_read_size = status.local_read_size + status.neighbour_read_size + status.remote_read_size
|
||||
})
|
||||
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)
|
||||
|
||||
function getStats(order) {
|
||||
lastOrder = order
|
||||
@@ -179,14 +179,14 @@ onDestroy(() => {
|
||||
<h3>Bandwidth usage and file views</h3>
|
||||
</section>
|
||||
<div class="highlight_border" style="margin-bottom: 6px;">
|
||||
<button on:click={() => loadGraph(1440, 1, true)}>Day 1m</button>
|
||||
<button on:click={() => loadGraph(10080, 10, true)}>Week 10m</button>
|
||||
<button on:click={() => loadGraph(43200, 60, true)}>Month 1h</button>
|
||||
<button on:click={() => loadGraph(131400, 1440, false)}>Quarter 1d</button>
|
||||
<button on:click={() => loadGraph(262800, 1440, false)}>Half-year 1d</button>
|
||||
<button on:click={() => loadGraph(525600, 1440, false)}>Year 1d</button>
|
||||
<button on:click={() => loadGraph(1051200, 1440, false)}>Two Years 1d</button>
|
||||
<button on:click={() => loadGraph(2628000, 1440, false)}>Five Years 1d</button>
|
||||
<button onclick={() => loadGraph(1440, 1, true)}>Day 1m</button>
|
||||
<button onclick={() => loadGraph(10080, 10, true)}>Week 10m</button>
|
||||
<button onclick={() => loadGraph(43200, 60, true)}>Month 1h</button>
|
||||
<button onclick={() => loadGraph(131400, 1440, false)}>Quarter 1d</button>
|
||||
<button onclick={() => loadGraph(262800, 1440, false)}>Half-year 1d</button>
|
||||
<button onclick={() => loadGraph(525600, 1440, false)}>Year 1d</button>
|
||||
<button onclick={() => loadGraph(1051200, 1440, false)}>Two Years 1d</button>
|
||||
<button onclick={() => loadGraph(2628000, 1440, false)}>Five Years 1d</button>
|
||||
</div>
|
||||
<Chart bind:this={graphBandwidth} data_type="bytes" />
|
||||
<Chart bind:this={graphViews} data_type="number" />
|
||||
@@ -306,22 +306,22 @@ onDestroy(() => {
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<button on:click={() => { getStats('query_name') }}>
|
||||
<button onclick={() => { getStats('query_name') }}>
|
||||
Query
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('calls') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('calls') }}>
|
||||
Calls
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('average_duration') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('average_duration') }}>
|
||||
Avg
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button style="cursor: pointer;" on:click={() => { getStats('total_duration') }}>
|
||||
<button style="cursor: pointer;" onclick={() => { getStats('total_duration') }}>
|
||||
Total
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { preventDefault, stopPropagation } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
@@ -15,13 +16,13 @@ const abuse_types = [
|
||||
"revenge_porn",
|
||||
]
|
||||
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
let rows = $state([])
|
||||
let total_offences = $state(0)
|
||||
|
||||
let expanded = false
|
||||
let creating = false
|
||||
let new_ban_address
|
||||
let new_ban_reason = abuse_types[0]
|
||||
let expanded = $state(false)
|
||||
let creating = $state(false)
|
||||
let new_ban_address = $state()
|
||||
let new_ban_reason = $state(abuse_types[0])
|
||||
|
||||
const get_bans = async () => {
|
||||
loading_start()
|
||||
@@ -103,20 +104,20 @@ onMount(get_bans);
|
||||
Offences {total_offences}
|
||||
</div>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button class:button_highlight={expanded} on:click={() => {expanded = !expanded}}>
|
||||
<button class:button_highlight={expanded} onclick={() => {expanded = !expanded}}>
|
||||
{#if expanded}
|
||||
<i class="icon">unfold_less</i> Collapse all
|
||||
{:else}
|
||||
<i class="icon">unfold_more</i> Expand all
|
||||
{/if}
|
||||
</button>
|
||||
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
|
||||
<button class:button_highlight={creating} onclick={() => {creating = !creating}}>
|
||||
<i class="icon">create</i> Add IP ban
|
||||
</button>
|
||||
</div>
|
||||
{#if creating}
|
||||
<div class="highlight_shaded">
|
||||
<form on:submit|preventDefault={create_ban}>
|
||||
<form onsubmit={preventDefault(create_ban)}>
|
||||
<div class="form">
|
||||
<label for="field_address">IP address</label>
|
||||
<input id="field_address" type="text" bind:this={new_ban_address}/>
|
||||
@@ -138,20 +139,22 @@ onMount(get_bans);
|
||||
|
||||
{#each rows as row (row.address)}
|
||||
<Expandable expanded={expanded} click_expand>
|
||||
<div slot="header" class="header">
|
||||
<div class="title">{row.address}</div>
|
||||
<div class="stats">
|
||||
Offences<br/>
|
||||
{row.offences.length}
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.address}</div>
|
||||
<div class="stats">
|
||||
Offences<br/>
|
||||
{row.offences.length}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button onclick={stopPropagation(() => {delete_ban(row.address)})} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button on:click|stopPropagation={() => {delete_ban(row.address)}} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div class="table_scroll">
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@@ -7,10 +7,10 @@ import { country_name, get_admin_invoices, type Invoice } from "lib/AdminAPI";
|
||||
import PayPalVat from "./PayPalVAT.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let invoices: Invoice[] = []
|
||||
let invoices: Invoice[] = $state([])
|
||||
|
||||
let year = 0
|
||||
let month = 0
|
||||
let year = $state(0)
|
||||
let month = $state(0)
|
||||
|
||||
type Total = {
|
||||
count: number
|
||||
@@ -18,8 +18,8 @@ type Total = {
|
||||
vat: number
|
||||
fee: number
|
||||
}
|
||||
let totals_provider: { [id: string]: Total } = {}
|
||||
let totals_country: { [id: string]: Total } = {}
|
||||
let totals_provider: { [id: string]: Total } = $state({})
|
||||
let totals_country: { [id: string]: Total } = $state({})
|
||||
const add_total = (i: Invoice) => {
|
||||
if (totals_provider[i.payment_method] === undefined) {
|
||||
totals_provider[i.payment_method] = {count: 0, amount: 0, vat: 0, fee: 0}
|
||||
@@ -119,14 +119,14 @@ onMount(() => {
|
||||
get_invoices()
|
||||
})
|
||||
|
||||
let status_filter = {
|
||||
let status_filter = $state({
|
||||
canceled: {checked: false},
|
||||
expired: {checked: false},
|
||||
open: {checked: false},
|
||||
paid: {checked: true},
|
||||
}
|
||||
let gateway_filter = {}
|
||||
let method_filter = {}
|
||||
})
|
||||
let gateway_filter = $state({})
|
||||
let method_filter = $state({})
|
||||
|
||||
const filter_invoices = () => {
|
||||
records_hidden = 0
|
||||
@@ -153,26 +153,28 @@ const filter_invoices = () => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
let records_hidden = 0
|
||||
let invoices_filtered: Invoice[] = []
|
||||
let records_hidden = $state(0)
|
||||
let invoices_filtered: Invoice[] = $state([])
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h3>{year + "-" + ("00"+(month)).slice(-2)}</h3>
|
||||
<div class="toolbar">
|
||||
<button on:click={last_month}>
|
||||
<button onclick={last_month}>
|
||||
<i class="icon">chevron_left</i>
|
||||
Previous month
|
||||
</button>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button on:click={next_month}>
|
||||
<button onclick={next_month}>
|
||||
Next month
|
||||
<i class="icon">chevron_right</i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">Per payment processor</div>
|
||||
{#snippet header()}
|
||||
<div class="header">Per payment processor</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list(totals_provider)}
|
||||
@@ -188,7 +190,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">Per country</div>
|
||||
{#snippet header()}
|
||||
<div class="header">Per country</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list(totals_country)}
|
||||
@@ -204,7 +208,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">In European Union</div>
|
||||
{#snippet header()}
|
||||
<div class="header">In European Union</div>
|
||||
{/snippet}
|
||||
<SortableTable
|
||||
index_field="id"
|
||||
rows={obj_to_list_eu(totals_country)}
|
||||
@@ -220,7 +226,9 @@ let invoices_filtered: Invoice[] = []
|
||||
</Expandable>
|
||||
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">PayPal VAT</div>
|
||||
{#snippet header()}
|
||||
<div class="header">PayPal VAT</div>
|
||||
{/snippet}
|
||||
<PayPalVat invoices={invoices}/>
|
||||
</Expandable>
|
||||
|
||||
@@ -233,7 +241,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="status_{filter}"
|
||||
bind:checked={status_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="status_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
@@ -245,7 +253,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="gateway_{filter}"
|
||||
bind:checked={gateway_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="gateway_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
@@ -257,7 +265,7 @@ let invoices_filtered: Invoice[] = []
|
||||
type="checkbox"
|
||||
id="method_{filter}"
|
||||
bind:checked={method_filter[filter].checked}
|
||||
on:change={filter_invoices}>
|
||||
onchange={filter_invoices}>
|
||||
<label for="method_{filter}">{filter}</label>
|
||||
<br/>
|
||||
{/each}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import { mollie_proxy_call } from "./MollieAPI";
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { loading_start } from "lib/Loading";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
export let settlement = {}
|
||||
let payments = []
|
||||
let { settlement = {} } = $props();
|
||||
let payments = $state([])
|
||||
|
||||
let per_country = {}
|
||||
let totals = {
|
||||
let per_country = $state({})
|
||||
let totals = $state({
|
||||
count: 0,
|
||||
vat: 0,
|
||||
amount: 0,
|
||||
}
|
||||
})
|
||||
|
||||
const load_all_payments = async (settlement_id) => {
|
||||
let payments = []
|
||||
|
||||
@@ -8,7 +8,7 @@ import { mollie_proxy_call } from "./MollieAPI";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let response = {}
|
||||
let settlements = []
|
||||
let settlements = $state([])
|
||||
|
||||
const get_settlements = async () => {
|
||||
loading_start()
|
||||
@@ -32,21 +32,23 @@ onMount(get_settlements);
|
||||
<section>
|
||||
{#each settlements as row (row.id)}
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.createdAt, false, false, false)}
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.createdAt, false, false, false)}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Amount<br/>
|
||||
<Euro amount={row.amount.value*1e6}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Amount<br/>
|
||||
<Euro amount={row.amount.value*1e6}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<MollieSettlement settlement={row}/>
|
||||
</Expandable>
|
||||
|
||||
@@ -6,15 +6,15 @@ import Euro from "util/Euro.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let response = {}
|
||||
let payments = []
|
||||
let payments = $state([])
|
||||
|
||||
let per_country = {}
|
||||
let totals = {}
|
||||
let per_country = $state({})
|
||||
let totals = $state({})
|
||||
|
||||
let datePicker
|
||||
let rangeMonths = 1
|
||||
let startDate = 0
|
||||
let endDate = 0
|
||||
let datePicker = $state()
|
||||
let rangeMonths = $state(1)
|
||||
let startDate = $state(0)
|
||||
let endDate = $state(0)
|
||||
|
||||
const get_payments = async () => {
|
||||
if (!datePicker.valueAsDate) {
|
||||
@@ -125,7 +125,7 @@ onMount(() => {
|
||||
<input type="date" bind:this={datePicker}/>
|
||||
<div>Months</div>
|
||||
<input type="number" bind:value={rangeMonths}/>
|
||||
<button on:click={get_payments}>Go</button>
|
||||
<button onclick={get_payments}>Go</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -227,29 +227,31 @@ onMount(() => {
|
||||
<h2>Payments</h2>
|
||||
{#each payments as row (row.id)}
|
||||
<Expandable click_expand>
|
||||
<div slot="header" class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.createdAt, false, false, false)}
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">{row.id}</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.createdAt, false, false, false)}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Total<br/>
|
||||
<Euro amount={row.amount.value*1e6}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Amount<br/>
|
||||
<Euro amount={row.metadata.amount}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
VAT<br/>
|
||||
<Euro amount={row.metadata.vat}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Total<br/>
|
||||
<Euro amount={row.amount.value*1e6}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Amount<br/>
|
||||
<Euro amount={row.metadata.amount}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
VAT<br/>
|
||||
<Euro amount={row.metadata.vat}/>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Status<br/>
|
||||
{row.status}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
<div>
|
||||
Amount: <Euro amount={row.metadata.amount} /><br/>
|
||||
VAT: <Euro amount={row.metadata.vat} /><br/>
|
||||
|
||||
@@ -78,18 +78,20 @@ const update_countries = (invoices: Invoice[]) => {
|
||||
{#if per_country["NL"] && totals}
|
||||
<h2>Summary</h2>
|
||||
<table style="width: auto;">
|
||||
<tr>
|
||||
<td>Total PayPal earnings -fees</td>
|
||||
<td><Euro amount={totals.vat+totals.amount-totals.fee}/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total VAT NL</td>
|
||||
<td><Euro amount={per_country["NL"].vat}/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total VAT OSS</td>
|
||||
<td><Euro amount={totals.vat-per_country["NL"].vat}/></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total PayPal earnings -fees</td>
|
||||
<td><Euro amount={totals.vat+totals.amount-totals.fee}/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total VAT NL</td>
|
||||
<td><Euro amount={per_country["NL"].vat}/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total VAT OSS</td>
|
||||
<td><Euro amount={totals.vat-per_country["NL"].vat}/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Accounting information</h2>
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
import { flip } from "svelte/animate";
|
||||
import { formatDataVolume } from "util/Formatting";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
|
||||
export let peers = [];
|
||||
$: update_peers(peers)
|
||||
let { peers = $bindable([]) } = $props();
|
||||
let update_peers = (peers) => {
|
||||
for (let peer of peers) {
|
||||
peer.avg_network_total = peer.avg_network_tx + peer.avg_network_rx
|
||||
peer.usage_percent = (peer.avg_network_tx / peer.port_speed) * 100
|
||||
peer.network_ratio = Math.max(peer.avg_network_tx, peer.avg_network_rx) / Math.min(peer.avg_network_tx, peer.avg_network_rx)
|
||||
if (peer.network_ratio === NaN) {
|
||||
peer.network_ratio = 1
|
||||
}
|
||||
}
|
||||
|
||||
sort("")
|
||||
}
|
||||
|
||||
let sort_field = "hostname"
|
||||
let asc = true
|
||||
let sort_field = $state("hostname")
|
||||
let asc = $state(true)
|
||||
let sort = (field) => {
|
||||
if (field !== "" && field === sort_field) {
|
||||
asc = !asc
|
||||
@@ -49,6 +46,9 @@ let sort = (field) => {
|
||||
})
|
||||
peers = peers
|
||||
}
|
||||
run(() => {
|
||||
update_peers(peers)
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="table_scroll">
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { formatDuration } from "util/Formatting";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let running_since = ""
|
||||
interface Props {
|
||||
running_since?: string;
|
||||
}
|
||||
|
||||
$: profile_running = running_since != "0001-01-01T00:00:00Z" && running_since != ""
|
||||
let { running_since = "" }: Props = $props();
|
||||
|
||||
let profile_running = $derived(running_since != "0001-01-01T00:00:00Z" && running_since != "")
|
||||
|
||||
const start = async () => {
|
||||
if (!profile_running) {
|
||||
const resp = await fetch(
|
||||
window.api_endpoint+"/admin/cpu_profile",
|
||||
get_endpoint()+"/admin/cpu_profile",
|
||||
{ method: "POST" }
|
||||
);
|
||||
if(resp.status >= 400) {
|
||||
throw new Error(await resp.text());
|
||||
}
|
||||
} else {
|
||||
window.open(window.api_endpoint+"/admin/cpu_profile")
|
||||
window.open(get_endpoint()+"/admin/cpu_profile")
|
||||
}
|
||||
|
||||
dispatch("refresh")
|
||||
}
|
||||
|
||||
let interval
|
||||
let running_time = "0s"
|
||||
let interval: number
|
||||
let running_time = $state("0s")
|
||||
onMount(() => {
|
||||
interval = setInterval(() => {
|
||||
if (profile_running) {
|
||||
running_time = formatDuration(
|
||||
(new Date()).getTime() - Date.parse(running_since),
|
||||
(new Date()).getTime() - Date.parse(running_since), 3
|
||||
)
|
||||
}
|
||||
}, 1000)
|
||||
@@ -43,7 +48,7 @@ onMount(() => {
|
||||
|
||||
<a class="button" href="/api/admin/call_stack">Call stack</a>
|
||||
<a class="button" href="/api/admin/heap_profile">Heap profile</a>
|
||||
<button on:click={start} class:button_red={profile_running}>
|
||||
<button onclick={start} class:button_red={profile_running}>
|
||||
{#if profile_running}
|
||||
Stop CPU profiling (running for {running_time})
|
||||
{:else}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Euro from "util/Euro.svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
|
||||
export let row = {}
|
||||
let { row = {} } = $props();
|
||||
</script>
|
||||
|
||||
<table>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { stopPropagation } from 'svelte/legacy';
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
import Expandable from "util/Expandable.svelte";
|
||||
@@ -8,9 +9,9 @@ import BanDetails from "./BanDetails.svelte";
|
||||
import UserLists from "./UserLists.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
|
||||
let rows = []
|
||||
let total_offences = 0
|
||||
let expanded = false
|
||||
let rows = $state([])
|
||||
let total_offences = $state(0)
|
||||
let expanded = $state(false)
|
||||
|
||||
const get_bans = async () => {
|
||||
loading_start()
|
||||
@@ -113,7 +114,7 @@ onMount(get_bans);
|
||||
Offences {total_offences}
|
||||
</div>
|
||||
<div class="toolbar_spacer"></div>
|
||||
<button class:button_highlight={expanded} on:click={() => {expanded = !expanded}}>
|
||||
<button class:button_highlight={expanded} onclick={() => {expanded = !expanded}}>
|
||||
{#if expanded}
|
||||
<i class="icon">unfold_less</i> Collapse all
|
||||
{:else}
|
||||
@@ -124,26 +125,28 @@ onMount(get_bans);
|
||||
|
||||
{#each rows as row (row.user_id)}
|
||||
<Expandable expanded={expanded} click_expand>
|
||||
<div slot="header" class="header">
|
||||
<div class="title">
|
||||
{row.user.username}
|
||||
{#snippet header()}
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
{row.user.username}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Type<br/>
|
||||
{row.offences[0].reason}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Count<br/>
|
||||
{row.offences.length}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button onclick={stopPropagation(() => {delete_ban(row.user_id)})} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Type<br/>
|
||||
{row.offences[0].reason}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Count<br/>
|
||||
{row.offences.length}
|
||||
</div>
|
||||
<div class="stats">
|
||||
Date<br/>
|
||||
{formatDate(row.offences[0].ban_time, false, false, false)}
|
||||
</div>
|
||||
<button on:click|stopPropagation={() => {delete_ban(row.user_id)}} class="button button_red" style="align-self: center;">
|
||||
<i class="icon">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="toolbar">
|
||||
<Button click={() => impersonate(row.user_id)} icon="login" label="Impersonate user"/>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||
import SortButton from "layout/SortButton.svelte";
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
|
||||
export let user_id = ""
|
||||
let files = []
|
||||
interface Props {
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
let { user_id = "" }: Props = $props();
|
||||
let files = $state([])
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
@@ -13,7 +18,7 @@ export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/files",
|
||||
get_endpoint()+"/user/files",
|
||||
{
|
||||
headers: {
|
||||
"Admin-User-Override": user_id,
|
||||
@@ -34,8 +39,8 @@ export const reload = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let sort_field = "date_upload"
|
||||
let asc = false
|
||||
let sort_field = $state("date_upload")
|
||||
let asc = $state(false)
|
||||
const sort = (field) => {
|
||||
if (field !== "" && field === sort_field) {
|
||||
asc = !asc
|
||||
@@ -84,7 +89,7 @@ const sort = (field) => {
|
||||
{#each files as file (file.id)}
|
||||
<tr>
|
||||
<td style="padding: 0; line-height: 1em;">
|
||||
<img src="{window.api_endpoint+file.thumbnail_href}?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
<img src="{get_endpoint()+file.thumbnail_href}?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="/u/{file.id}" target="_blank">{file.name}</a>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { loading_finish, loading_start } from "lib/Loading";
|
||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
||||
import { onMount } from "svelte";
|
||||
import { formatDate } from "util/Formatting";
|
||||
|
||||
export let user_id = ""
|
||||
let lists = []
|
||||
interface Props {
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
let { user_id = "" }: Props = $props();
|
||||
let lists = $state([])
|
||||
|
||||
onMount(() => reload())
|
||||
|
||||
@@ -12,7 +17,7 @@ export const reload = async () => {
|
||||
loading_start()
|
||||
try {
|
||||
const req = await fetch(
|
||||
window.api_endpoint+"/user/lists",
|
||||
get_endpoint()+"/user/lists",
|
||||
{
|
||||
headers: {
|
||||
"Admin-User-Override": user_id,
|
||||
@@ -47,7 +52,7 @@ export const reload = async () => {
|
||||
{#each lists as list (list.id)}
|
||||
<tr>
|
||||
<td style="padding: 0; line-height: 1em;">
|
||||
<img src="{window.api_endpoint}/list/{list.id}/thumbnail?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
<img src="{get_endpoint()}/list/{list.id}/thumbnail?height=48&width=48" alt="icon" class="thumbnail" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="/l/{list.id}" target="_blank">{list.title}</a>
|
||||
|
||||
Reference in New Issue
Block a user