Rename FNX to Nova
BIN
res/static/img/header.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
res/static/img/northernlights.webp
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
res/static/img/nova.xcf
Normal file
BIN
res/static/img/nova_1024.png
Normal file
|
After Width: | Height: | Size: 559 KiB |
BIN
res/static/img/nova_128.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/static/img/nova_256.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
res/static/img/nova_32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
res/static/img/nova_512.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
res/static/img/nova_64.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
res/static/img/nova_title.xcf
Normal 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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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("/")) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||