Update to svelte 5

This commit is contained in:
2025-10-13 16:05:50 +02:00
parent f936e4c0f2
commit 6d89c5ddd9
129 changed files with 2443 additions and 2180 deletions

View File

@@ -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}

View File

@@ -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>

View File

@@ -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}/>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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 = []

View File

@@ -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>

View File

@@ -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/>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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}

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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>

View File

@@ -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>