Rename FNX to Nova

This commit is contained in:
2026-01-26 22:23:36 +01:00
parent 70e8353a50
commit 70a22d4a06
22 changed files with 67 additions and 43 deletions

BIN
res/static/img/header.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
res/static/img/nova.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

BIN
res/static/img/nova_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
res/static/img/nova_256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
res/static/img/nova_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
res/static/img/nova_512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
res/static/img/nova_64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

View File

@@ -10,16 +10,17 @@
<link id="stylesheet_layout" rel="stylesheet" type="text/css" href="/res/style/layout.css?v{{cacheID}}"/> <link id="stylesheet_layout" rel="stylesheet" type="text/css" href="/res/style/layout.css?v{{cacheID}}"/>
<link id="stylesheet_theme" rel="stylesheet" type="text/css" href="/theme.css"/> <link id="stylesheet_theme" rel="stylesheet" type="text/css" href="/theme.css"/>
<link rel="icon" sizes="32x32" href="/res/img/pixeldrain_32.png" /> <link rel="icon" sizes="32x32" href="/res/img/nova_32.png" />
<link rel="icon" sizes="128x128" href="/res/img/pixeldrain_128.png" /> <link rel="icon" sizes="64x64" href="/res/img/nova_64.png" />
<link rel="icon" sizes="152x152" href="/res/img/pixeldrain_152.png" /> <link rel="icon" sizes="128x128" href="/res/img/nova_128.png" />
<link rel="icon" sizes="180x180" href="/res/img/pixeldrain_180.png" /> <link rel="icon" sizes="152x152" href="/res/img/nova_152.png" />
<link rel="icon" sizes="192x192" href="/res/img/pixeldrain_192.png" /> <link rel="icon" sizes="180x180" href="/res/img/nova_180.png" />
<link rel="icon" sizes="196x196" href="/res/img/pixeldrain_196.png" /> <link rel="icon" sizes="192x192" href="/res/img/nova_192.png" />
<link rel="icon" sizes="256x256" href="/res/img/pixeldrain_256.png" /> <link rel="icon" sizes="196x196" href="/res/img/nova_196.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/res/img/pixeldrain_152.png" /> <link rel="icon" sizes="256x256" href="/res/img/nova_256.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/res/img/pixeldrain_180.png" /> <link rel="apple-touch-icon" sizes="128x128" href="/res/img/nova_128.png" />
<link rel="shortcut icon" sizes="196x196" href="/res/img/pixeldrain_196.png" /> <link rel="apple-touch-icon" sizes="256x256" href="/res/img/nova_256.png" />
<link rel="shortcut icon" sizes="256x256" href="/res/img/nova_256.png" />
{{ template "opengraph" .OGData }} {{ template "opengraph" .OGData }}
<script> <script>

View File

@@ -97,7 +97,7 @@ export class FSNavigator {
// we still replace the URL with replaceState. This way the user is not // we still replace the URL with replaceState. This way the user is not
// greeted to a 404 page when refreshing after renaming a file // greeted to a 404 page when refreshing after renaming a file
if (this.history_enabled) { if (this.history_enabled) {
window.document.title = node.path[node.base_index].name + " / FNX" window.document.title = node.path[node.base_index].name + " / Nova"
const url = "/d" + fs_encode_path(node.path[node.base_index].path) + window.location.hash const url = "/d" + fs_encode_path(node.path[node.base_index].path) + window.location.hash
if (push_history) { if (push_history) {
window.history.pushState({}, window.document.title, url) window.history.pushState({}, window.document.title, url)

View File

@@ -387,15 +387,7 @@ run(() => {
{#if creating_dir} {#if creating_dir}
<CreateDirectory nav={nav} /> <CreateDirectory nav={nav} />
{/if} {/if}
{#if $nav.base.path === "/me"}
<div class="highlight_shaded" style="background-color: rgba(255, 255, 0, 0.05); border-radius: 0;">
The filesystem is experimental!
<a href="/filesystem">Please read the guide</a>
</div> </div>
{/if}
</div>
{#if $nav.base.abuse_type !== undefined} {#if $nav.base.abuse_type !== undefined}
<div class="highlight_red"> <div class="highlight_red">

View File

@@ -22,7 +22,7 @@ let upload_widget
<div class="page_content"> <div class="page_content">
<section> <section>
<p> <p>
FNX.storage is a platform for cost-effective cloud storage and Nova.storage is a platform for cost-effective cloud storage and
content delivery. We will store and serve your files at an extremely content delivery. We will store and serve your files at an extremely
competitive rate. competitive rate.
</p> </p>
@@ -36,7 +36,7 @@ let upload_widget
<div>€ 1 / TB</div> <div>€ 1 / TB</div>
</div> </div>
</div> </div>
<h2>What FNX is good at</h2> <h2>What Nova is good at</h2>
<ul> <ul>
<li> <li>
Serving large files to millions of people worldwide Serving large files to millions of people worldwide
@@ -147,11 +147,11 @@ header > h1 {
.header_image_container { .header_image_container {
text-align: initial; text-align: initial;
margin: auto; margin: auto;
margin-bottom: 1.5em; /*Offset for menu button*/ margin-bottom: 50px;
height: 150px; height: 100px;
width: 500px; width: 500px;
max-width: 100%; max-width: 100%;
background-image: url("/res/img/header_orbitron.webp"); background-image: url("/res/img/header.webp");
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain; background-size: contain;
background-position: center; background-position: center;

View File

@@ -18,7 +18,7 @@ let egress = $state(10) // TB
let avg_file_size = $state(1000) // kB let avg_file_size = $state(1000) // kB
$effect(() => { $effect(() => {
// FNX has a minimum file size of 10kB. Calculate the number of files from // Nova has a minimum file size of 10kB. Calculate the number of files from
// storage and avg file size, then calculate storage usage based on that. // storage and avg file size, then calculate storage usage based on that.
fnx_storage = Math.max(storage * 4, ((storage*10)/avg_file_size)*4) fnx_storage = Math.max(storage * 4, ((storage*10)/avg_file_size)*4)
fnx_egress = egress * 1 fnx_egress = egress * 1
@@ -67,14 +67,14 @@ $effect(() => {
<div class="bars"> <div class="bars">
<div> <div>
<div> <div>
FNX.storage - <Euro amount={fnx_total*1e6}/> / month<br/> Nova.storage - <Euro amount={fnx_total*1e6}/> / month<br/>
<Euro amount={fnx_storage*1e6}/> storage, <Euro amount={fnx_storage*1e6}/> storage,
<Euro amount={fnx_egress*1e6}/> egress <Euro amount={fnx_egress*1e6}/> egress
<ProgressBar used={fnx_total} total={price_max}/> <ProgressBar used={fnx_total} total={price_max}/>
</div> </div>
{#if avg_file_size < 10} {#if avg_file_size < 10}
<div> <div>
FNX counts a minimum file size of 10 kB. Files smaller than that Nova counts a minimum file size of 10 kB. Files smaller than that
are rounded up to 10 kB. are rounded up to 10 kB.
</div> </div>
{/if} {/if}
@@ -105,16 +105,16 @@ $effect(() => {
</div> </div>
<p> <p>
Note that while FNX.storage might not seem to be the cheapest option in some Note that while Nova.storage might not seem to be the cheapest option in some
cases, most cloud providers have extra hidden costs for API calls and cases, most cloud providers have extra hidden costs for API calls and
region-specific prices. This makes it very hard to accurately compare the region-specific prices. This makes it very hard to accurately compare the
pricing of these platforms. FNX.storage includes no hidden costs, I only pricing of these platforms. Nova.storage includes no hidden costs, I only
charge for storage and egress. charge for storage and egress.
</p> </p>
<p> <p>
Large cloud providers like Amazon, Microsoft and Google are excluded from Large cloud providers like Amazon, Microsoft and Google are excluded from
this calculation because their pricing is too complex accurately compare this calculation because their pricing is too complex accurately compare
them. Just assume that FNX wil be cheaper. them. Just assume that Nova wil be cheaper.
</p> </p>
<style> <style>

View File

@@ -88,7 +88,8 @@ const login = async (e?: SubmitEvent) => {
{ {
method: "POST", method: "POST",
body: fd, body: fd,
credentials: "omit", // Dont send existing session cookies // Allow server to set cookies
credentials: "include",
}, },
)) ))
@@ -99,9 +100,6 @@ const login = async (e?: SubmitEvent) => {
} }
} }
// Save the session cookie
document.cookie = "pd_auth_key="+resp.auth_key+"; Max-Age=31536000;"
dispatch("login", {key: resp.auth_key}) dispatch("login", {key: resp.auth_key})
if (typeof login_redirect === "string" && login_redirect.startsWith("/")) { if (typeof login_redirect === "string" && login_redirect.startsWith("/")) {

View File

@@ -2,6 +2,7 @@
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from 'svelte/legacy';
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
import { formatDate } from "util/Formatting"; import { formatDate } from "util/Formatting";
import CopyButton from 'layout/CopyButton.svelte';
let loaded = $state(false) let loaded = $state(false)
let rows = $state([]) let rows = $state([])
@@ -130,12 +131,15 @@ const logout = async (key) => {
<tbody> <tbody>
{#each rows as row (row.auth_key)} {#each rows as row (row.auth_key)}
<tr style="border-bottom: none;"> <tr style="border-bottom: none;">
<td>{row.auth_key}</td> <td>
<CopyButton text={row.auth_key} small_icon>Copy</CopyButton>
{row.auth_key}
</td>
<td>{formatDate(row.creation_time, true, true, false)}</td> <td>{formatDate(row.creation_time, true, true, false)}</td>
<td>{formatDate(row.last_used_time, true, true, false)}</td> <td>{formatDate(row.last_used_time, true, true, false)}</td>
<td>{row.creation_ip_address}</td> <td>{row.creation_ip_address}</td>
<td> <td>
<button onclick={preventDefault(() => {logout(row.auth_key)})} class="button button_red round"> <button onclick={(e) => {e.preventDefault();logout(row.auth_key)}} class="button button_red round">
<i class="icon">delete</i> <i class="icon">delete</i>
</button> </button>
</td> </td>

View File

@@ -3,6 +3,7 @@ import { get_endpoint, get_user, type User } from "lib/PixeldrainAPI";
import { onMount } from "svelte"; import { onMount } from "svelte";
import HotlinkProgressBar from "user_home/HotlinkProgressBar.svelte"; import HotlinkProgressBar from "user_home/HotlinkProgressBar.svelte";
import StorageProgressBar from "user_home/StorageProgressBar.svelte"; import StorageProgressBar from "user_home/StorageProgressBar.svelte";
import { formatThousands } from "util/Formatting";
let transfer_cap = $state(0) let transfer_cap = $state(0)
let transfer_used = $state(0) let transfer_used = $state(0)
@@ -53,6 +54,9 @@ onMount(async () => {
<StorageProgressBar used={user.storage_space_used} total={storage_limit}/> <StorageProgressBar used={user.storage_space_used} total={storage_limit}/>
<br/> <br/>
File count: {formatThousands(user.filesystem_node_count)}
<br/><br/>
Egress used (30 days): Egress used (30 days):
(<a href="/user/sharing/bandwidth">set custom limit</a>) (<a href="/user/sharing/bandwidth">set custom limit</a>)
<HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar> <HotlinkProgressBar used={transfer_used} total={transfer_cap}></HotlinkProgressBar>

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { formatDataVolume, formatNumber } from "./Formatting"; import { formatDataVolume, formatDuration, formatNumber } from "./Formatting";
import { color_by_name } from "./Util"; import { color_by_name } from "./Util";
import { import {
Chart, Chart,
@@ -33,12 +33,14 @@ let {
legend = true, legend = true,
tooltips = true, tooltips = true,
ticks = true, ticks = true,
animations = true,
height = "300px" height = "300px"
}: { }: {
data_type?: string; data_type?: string;
legend?: boolean; legend?: boolean;
tooltips?: boolean; tooltips?: boolean;
ticks?: boolean; ticks?: boolean;
animations?: boolean;
height?: string; height?: string;
} = $props(); } = $props();
@@ -85,6 +87,9 @@ onMount(() => {
}, },
tooltip: { tooltip: {
enabled: tooltips, enabled: tooltips,
itemSort: (a, b): number => {
return <number>b.raw - <number>a.raw
},
}, },
}, },
layout: { layout: {
@@ -102,6 +107,8 @@ onMount(() => {
callback: function (value: number, index: number, values: Tick[]) { callback: function (value: number, index: number, values: Tick[]) {
if (data_type == "bytes") { if (data_type == "bytes") {
return formatDataVolume(value, 3); return formatDataVolume(value, 3);
} else if (data_type === "duration") {
return formatDuration(value, 2);
} }
return formatNumber(value, 3); return formatNumber(value, 3);
}, },
@@ -129,6 +136,11 @@ onMount(() => {
} }
} }
); );
if (!animations) {
chart_object.options.animation = false
chart_object.options.transitions.active.animation.duration = 0
}
}) })
</script> </script>

View File

@@ -58,11 +58,24 @@ const hour = minute * 60
const day = hour * 24 const day = hour * 24
export const formatDuration = (ms: number, decimals: number) => { export const formatDuration = (ms: number, decimals: number) => {
let remainingDecimals = decimals
let res = "" let res = ""
if (ms >= day) { res += Math.floor(ms / day) + "d " } if (ms >= day && remainingDecimals > 0) {
if (ms >= hour) { res += Math.floor((ms % day) / hour) + "h " } res += Math.floor(ms / day) + "d "
if (ms >= minute) { res += Math.floor((ms % hour) / minute) + "m " } remainingDecimals--
return res + ((ms % minute) / second).toFixed(decimals) + "s" }
if (ms >= hour && remainingDecimals > 0) {
res += Math.floor((ms % day) / hour) + "h "
remainingDecimals--
}
if (ms >= minute && remainingDecimals > 0) {
res += Math.floor((ms % hour) / minute) + "m "
remainingDecimals--
}
if (remainingDecimals > 0) {
res += ((ms % minute) / second).toFixed(remainingDecimals) + "s"
}
return res
} }
export const formatDate = ( export const formatDate = (

View File

@@ -49,7 +49,7 @@ const get_page = () => {
} }
title = current_subpage === null ? current_page.title : current_subpage.title title = current_subpage === null ? current_page.title : current_subpage.title
window.document.title = title+" / FNX" window.document.title = title+" / Nova"
} }
let current_page: Tab = $state(null) let current_page: Tab = $state(null)

View File

@@ -98,7 +98,7 @@ const load_page = (pathname: string, history: boolean): boolean => {
return load_page("/login", true) return load_page("/login", true)
} }
window.document.title = current_page.title+" / FNX" window.document.title = current_page.title+" / Nova"
if(history) { if(history) {
window.history.pushState({}, window.document.title, pathname) window.history.pushState({}, window.document.title, pathname)