Fully rename Pixeldrain to Nova
12
README.md
@@ -1,23 +1,23 @@
|
|||||||
# pixeldrain_web
|
# nova_web
|
||||||
|
|
||||||
Web interface for pixeldrain.com
|
Web interface for Nova.storage
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
Clone the repo:
|
Clone the repo:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone git@github.com:Fornaxian/pixeldrain_web.git
|
git clone git@scm.fornaxian.tech:pixeldrain/fnx_web.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Enter the directory and run main.go with `go run main.go`. It will generate a
|
Enter the directory and run main.go with `go run main.go`. It will generate a
|
||||||
configuration file for you. The default configuration serves the web UI on
|
configuration file for you. The default configuration serves the web UI on
|
||||||
http://127.0.0.1:8081. It contains a reverse proxy server which sends all API
|
http://127.0.0.1:8081. It contains a reverse proxy server which sends all API
|
||||||
requests to the production endpoint at https://pixeldrain.com/api. You can log
|
requests to the production endpoint at https://nova.storage/api. You can log
|
||||||
in with your real pixeldrain account on your development server by going to
|
in with your real Nova account on your development server by going to
|
||||||
http://127.0.0.1:8081/login.
|
http://127.0.0.1:8081/login.
|
||||||
|
|
||||||
All except for one of pixeldrain's API endpoints are publicly available. Because
|
All except for one of Nova's API endpoints are publicly available. Because
|
||||||
of this you can do everything with the locally hosted instance which you can do
|
of this you can do everything with the locally hosted instance which you can do
|
||||||
with the real site. The one thing which is missing is the view registration on
|
with the real site. The one thing which is missing is the view registration on
|
||||||
the file viewer. Views are verified on the server side, this does not work when
|
the file viewer. Views are verified on the server side, this does not work when
|
||||||
|
|||||||
@@ -1,628 +0,0 @@
|
|||||||
# Fornax's Guide To Ridiculously Fast Ethernet
|
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [Sysctls](#sysctls)
|
|
||||||
- [net.ipv4.tcp_congestion_control](#net-ipv4-tcp-congestion-control)
|
|
||||||
- [net.core.default_qdisc and txqueuelen](#net-core-default-qdisc-and-txqueuelen)
|
|
||||||
- [net.ipv4.tcp_shrink_window](#net-ipv4-tcp-shrink-window)
|
|
||||||
- [net.ipv4.tcp_{w,r}mem](#net-ipv4-tcp-w-r-mem)
|
|
||||||
- [net.ipv4.tcp_mem](#net-ipv4-tcp-mem)
|
|
||||||
- [Network Interface Cards](#network-interface-cards)
|
|
||||||
- [ethtool](#ethtool)
|
|
||||||
- [Channels (ethtool -l)](#channels-ethtool-l)
|
|
||||||
- [Ring buffers (ethtool -g)](#ring-buffers-ethtool-g)
|
|
||||||
- [Interrupt Coalescing (ethtool -c)](#interrupt-coalescing-ethtool-c)
|
|
||||||
- [BIOS](#bios)
|
|
||||||
- [NUMA Nodes per socket](#numa-nodes-per-socket)
|
|
||||||
- [SMT Control](#smt-control)
|
|
||||||
- [IOMMU](#iommu)
|
|
||||||
- [Reverse proxy](#reverse-proxy)
|
|
||||||
- [HTTP/2 or QUIC?](#http-2-or-quic)
|
|
||||||
- [Operating system](#operating-system)
|
|
||||||
- [Kernel](#kernel)
|
|
||||||
- [That's all, folks!](#that-s-all-folks)
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
If you are one of the lucky few who has a fast enough connection, you might have
|
|
||||||
just downloaded a 5 GB file in 10 seconds and wondered how that is even
|
|
||||||
possible. Well, it took a lot of effort to get there.
|
|
||||||
|
|
||||||
When I first ordered a 100 GbE server I expected things to just work. Imagine my
|
|
||||||
surprise when the server crashed when serving at just 20 Gigabit.
|
|
||||||
|
|
||||||
Then I expected my server host to be able to help with the performance problems.
|
|
||||||
Spoiler alert: They could not help me.
|
|
||||||
|
|
||||||
That's where my journey into the rabbit hole of network performance started. I
|
|
||||||
paid good money for that 100 Gigabit connection and I'll be damned if I can't
|
|
||||||
use all of it. I'm getting to the bottom of this no matter how long it takes...
|
|
||||||
|
|
||||||
It took me well over a year to figure out all the details of high speed
|
|
||||||
networking. My good friend [Jeff
|
|
||||||
Brandt](https://www.linkedin.com/in/jeff-brandt-51b2a65/) (who has been hosting
|
|
||||||
pixeldrain for nearly ten years now) was able to point me in the right direction
|
|
||||||
by explaining the basics and showing me some sysctls and ethtool commands which
|
|
||||||
might affect performance. That was just the entrance of the rabbit hole though,
|
|
||||||
and this one carried on deep. After about a year of trial and error pixeldrain
|
|
||||||
can finally serve files at 100 Gigabit per second.
|
|
||||||
|
|
||||||
Below is a summary of everything I discovered during my year of reading NIC
|
|
||||||
manuals, digging through the kernel sources, running profilers, patching the
|
|
||||||
kernel, learning about CPU topology and TCP inner workings.
|
|
||||||
|
|
||||||
## Sysctls
|
|
||||||
|
|
||||||
When looking into network performance problems the `sysctl`s are usually the
|
|
||||||
first thing you get pointed at. There is **a ton** of conflicting information
|
|
||||||
online about which sysctls do what and what to set them to.
|
|
||||||
|
|
||||||
Sysctls are not persistent through reboots, add these lines to
|
|
||||||
`/etc/sysctl.conf` to apply them at startup.
|
|
||||||
|
|
||||||
Through experimentation and kernel recompilation I finally settled on these
|
|
||||||
values:
|
|
||||||
|
|
||||||
### net.ipv4.tcp_congestion_control
|
|
||||||
|
|
||||||
You might have heard of BBR. Google's new revolutionary congestion control
|
|
||||||
algorithm. You might have heard conflicting information about how good it is. I
|
|
||||||
have extensively tested all congestion controls in the kernel and I can say
|
|
||||||
without a doubt that BBR is the best, by far! BBR is the only algo which does
|
|
||||||
not absolutely tank your transfer rate when a packet is lost.
|
|
||||||
|
|
||||||
TCP BBR was merged into the kernel at version 4.9. I know the sysctl says ipv4,
|
|
||||||
but it works for IPv6 as well.
|
|
||||||
|
|
||||||
`net.ipv4.tcp_congestion_control=bbr`
|
|
||||||
|
|
||||||
### net.core.default_qdisc and txqueuelen
|
|
||||||
|
|
||||||
The qdisc (queuing discipline) is another param which gets mentioned often. The
|
|
||||||
qdisc orders packets which are queued so they can be sent in the most efficient
|
|
||||||
order possible. The thing is, when you're sending at 100 Gbps then queuing is
|
|
||||||
completely irrelevant, the network is rarely the bottleneck here.
|
|
||||||
|
|
||||||
Google used to require `fq` with `bbr`, but that requirement has been dropped. I
|
|
||||||
suggest you use something minimal and fast. How about `pfifo_fast`, it has fast
|
|
||||||
in the name, must be good, right? This is actually already the default on Linux
|
|
||||||
nowadays, so there's not really a need to change it.
|
|
||||||
|
|
||||||
`net.core.default_qdisc=pfifo_fast`
|
|
||||||
|
|
||||||
A queue must have a size though. Linux gives the network queues a size of 1000
|
|
||||||
packets by default. As we'll learn later, a thousand packets is really not a lot
|
|
||||||
when running at 100 Gbps. When the queue is full the kernel will actually drop
|
|
||||||
packets, which is absolutely not what we want. So we increase the queue length
|
|
||||||
to 10000 packets instead:
|
|
||||||
|
|
||||||
`ip link set $INTERFACE txqueuelen 10000`
|
|
||||||
|
|
||||||
### net.ipv4.tcp_shrink_window
|
|
||||||
|
|
||||||
This sysctl was developed by Cloudflare. The patch was merged into Linux 6.1. If
|
|
||||||
you are on an older kernel version than 6.1 you will need to manually apply [the
|
|
||||||
patches](https://github.com/cloudflare/linux/) and compile the kernel on your
|
|
||||||
machine. Without this patch the kernel will waste so much time and memory on
|
|
||||||
buffer management that by the time you reach 100 Gigabit the kernel won’t even
|
|
||||||
have time to run your app anymore.
|
|
||||||
|
|
||||||
Cloudflare has an extensive writeup about the problem this sysctl solves here:
|
|
||||||
[Unbounded memory usage by TCP for receive buffers, and how we fixed
|
|
||||||
it](https://blog.cloudflare.com/unbounded-memory-usage-by-tcp-for-receive-buffers-and-how-we-fixed-it/)
|
|
||||||
|
|
||||||
This sysctl makes sure that TCP buffers are shrunk if they are larger than they
|
|
||||||
need to be. Without this sysctl your buffers will just continue to grow until
|
|
||||||
memory runs out! Before I discovered this patch my servers would regularly run
|
|
||||||
out of memory during peak load, and these are servers with a **TeraByte of
|
|
||||||
RAM**! After applying the patches (and compiling the kernel, because the patches
|
|
||||||
were not merged yet back then) memory usage from TCP buffers was reduced by 80%
|
|
||||||
on my systems. And performance has improved considerably. This patch is so
|
|
||||||
crucial for performance that it boggles my mind that it's not enabled by
|
|
||||||
default. It's even described in the [TCP
|
|
||||||
spec](https://www.rfc-editor.org/rfc/rfc7323#section-2.4), it's standardized
|
|
||||||
behaviour. If you're a kernel or systemd developer, please consider just turning
|
|
||||||
this on by default instead of hiding it behind a toggle.
|
|
||||||
|
|
||||||
`net.ipv4.tcp_shrink_window=1`
|
|
||||||
|
|
||||||
Cloudflare has some other sysctls as well, but those focus more on latency than
|
|
||||||
throughput. You can find them here: [Optimizing TCP for high WAN throughput
|
|
||||||
while preserving low
|
|
||||||
latency](https://blog.cloudflare.com/optimizing-tcp-for-high-throughput-and-low-latency/).
|
|
||||||
The `net.ipv4.tcp_collapse_max_bytes` sysctl they write about here was never
|
|
||||||
merged into the kernel. But while it does improve latency a bit, it's not that
|
|
||||||
important for throughput.
|
|
||||||
|
|
||||||
### net.ipv4.tcp_{w,r}mem
|
|
||||||
|
|
||||||
These variables dictate how much memory can be allocated for your send and
|
|
||||||
receive buffers. The send and receive buffers are where TCP packets are stored
|
|
||||||
which are not yet acknowledged by the peer. The required size of these buffers
|
|
||||||
depends on your [Bandwidth-Delay Product
|
|
||||||
(BDP)](https://en.wikipedia.org/wiki/Bandwidth-delay_product). This concept is
|
|
||||||
crucial to understand. If you set the TCP buffers too small it will literally
|
|
||||||
put a speed limit on your connection.
|
|
||||||
|
|
||||||
First let's go over how TCP sends data. TCP can retransmit packets if the client
|
|
||||||
did not receive them. To do this TCP needs to keep all the data it sends to the
|
|
||||||
client in memory until the client acknowledges (ACK) that it has been properly
|
|
||||||
received. The acknowledgment takes one round trip to the client and back.
|
|
||||||
|
|
||||||
Let's say you want to send a file from Amsterdam to Tokyo. The server sends the
|
|
||||||
first packet, 130ms later the client in Tokyo receives the data packet. The
|
|
||||||
client then sends ACK to tell the server that the packet was properly received,
|
|
||||||
the ACK takes 130ms to arrive back in Amsterdam. Only now can the server remove
|
|
||||||
the packet from memory. The whole exchange took 260ms.
|
|
||||||
|
|
||||||
Now let's say we want to send files at 10 Gigabit. 10 Gigabit is 1250 MB. We
|
|
||||||
multiply the number of bytes we want to send per second by the number of seconds
|
|
||||||
it takes to get back the ACK. That's `1250 MB * 0.260 s = 325 MB`. Now we know
|
|
||||||
that our buffer needs to be at least 325 MB to reach a speed of 10 Gigabit over
|
|
||||||
a 260ms round trip.
|
|
||||||
|
|
||||||
The kernel also stores some other TCP-related stuff in that memory, and we also
|
|
||||||
need to account for packet loss which causes packets to be stored for a longer
|
|
||||||
time. I also don't want the speed to be limited to 10 Gbps, we're running a 100
|
|
||||||
GbE NIC after all. For this reason pixeldrain servers use a maximum buffer size
|
|
||||||
of 1 GiB.
|
|
||||||
|
|
||||||
```
|
|
||||||
net.ipv4.tcp_wmem='4096 65536 1073741824'
|
|
||||||
net.core.wmem_max=1073741824
|
|
||||||
net.ipv4.tcp_rmem='4096 65536 1073741824'
|
|
||||||
net.core.rmem_max=1073741824
|
|
||||||
```
|
|
||||||
|
|
||||||
The three values in the wmem and rmem are the minimum buffer size, the default
|
|
||||||
buffer size and the maximum buffer size. The pixeldrain server application uses
|
|
||||||
64k reusable buffers (with [sync.Pool](https://pkg.go.dev/sync#Pool)) all over
|
|
||||||
the codebase. For this reason we initialize the window size at 64k as well.
|
|
||||||
|
|
||||||
### net.ipv4.tcp_mem
|
|
||||||
|
|
||||||
We just configured the buffer sizes, what's this for then? Well... we can tune
|
|
||||||
TCP buffers per connection all we want, but all that is for nothing if the
|
|
||||||
kernel still limits the TCP buffers globally.
|
|
||||||
|
|
||||||
This sysctl configures how much system memory can be used for TCP buffers. On
|
|
||||||
boot these values are set based on available system memory, which is good. But
|
|
||||||
by default it only uses like 5% of the memory, which is not even close to
|
|
||||||
enough. We need to pump those numbers way up to get anywhere near the speed that
|
|
||||||
we want.
|
|
||||||
|
|
||||||
tcp_mem is defined as three separate values. These values are in numbers of
|
|
||||||
memory pages. A memory page is usually 4096B. Here is what these three values mean:
|
|
||||||
|
|
||||||
* `low`: When TCP memory is below this threshold then TCP buffer sizes are not
|
|
||||||
limited.
|
|
||||||
* `pressure`: When the TCP memory usage exceeds this threshold it will try to
|
|
||||||
shrink some TCP buffers to free up memory. It will keep doing this until
|
|
||||||
memory usage drops below `low` again. Shrinking TCP buffers takes a lot of
|
|
||||||
CPU time, and during this time no data is sent to the client. You don't want
|
|
||||||
to set `low` and `pressure` too far apart.
|
|
||||||
* `high`: The TCP system can't allocate more than this number of pages. If this
|
|
||||||
limit is reached and a new TCP session is opened it will not be able to
|
|
||||||
allocate any memory. Needless to say this is terrible for performance.
|
|
||||||
|
|
||||||
After a lot of experimentation with these values I have come to the conclusion
|
|
||||||
that the best values for these parameters are 40% of RAM, 50% of RAM and 60% of
|
|
||||||
RAM. This will use most of the RAM for TCP buffers if needed, but also leaves
|
|
||||||
plenty for your applications.
|
|
||||||
|
|
||||||
I set these values dynamically per host with Ansible:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
{{noescape `- name: configure tcp_mem
|
|
||||||
sysctl:
|
|
||||||
name: net.ipv4.tcp_mem
|
|
||||||
value: "{{ (mempages|int * 0.4)|int }} {{ (mempages|int * 0.5)|int }} {{ (mempages|int * 0.6)|int }}"
|
|
||||||
state: present
|
|
||||||
vars:
|
|
||||||
mempages: "{{ ansible_memtotal_mb * 256 }}" # There are 256 mempages in a MiB`}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Network Interface Cards
|
|
||||||
|
|
||||||
There are lots of NICs to choose from. From my testing every NIC seems to behave
|
|
||||||
differently. The only NIC types I have had any luck with are ConnectX-5 and
|
|
||||||
ConnectX-6. Intel's E810 NICs are also not terrible, but Nvidia cards seem to
|
|
||||||
fare much better with high connection counts. I currently have two servers with
|
|
||||||
E810 cards and two servers with ConnectX-6 cards. The E810 cards are usually the
|
|
||||||
first to crap out during a load peak. NICs are just fickle beasts overall. I
|
|
||||||
don't know if my experiences are actually related to the quality of the cards,
|
|
||||||
or just bad luck with faulty hardware.
|
|
||||||
|
|
||||||
Often you see advice to install a proprietary driver for your NIC. Don't do
|
|
||||||
that. In my experience that has only caused problems. Nvidia's NIC drivers are
|
|
||||||
just as shitty as their video drivers. They will break kernel updates and
|
|
||||||
generally make your life miserable. The drivers in the Linux kernel are good and
|
|
||||||
well maintained. You don't need to taint your kernel with some scary proprietary
|
|
||||||
blob.
|
|
||||||
|
|
||||||
Upgrading the firmware for your NIC can be a good idea, if you can figure out
|
|
||||||
how, that is. Nvidia's tools for upgrading firmware are a huge hassle to work
|
|
||||||
with and the documentation is outdated and scarce.
|
|
||||||
|
|
||||||
## ethtool
|
|
||||||
|
|
||||||
Ethtool is a program which you can use to configure your network card. There is
|
|
||||||
lots of stuff to configure here, but there are only three settings which really
|
|
||||||
matter.
|
|
||||||
|
|
||||||
Ethtool needs your network interface name for every operation. In this guide we
|
|
||||||
will refer to your interface name as `$INTERFACE`. You can get your interface
|
|
||||||
name from `ip a`.
|
|
||||||
|
|
||||||
Ethtool options are not persistent through reboots. And there's no configuration
|
|
||||||
file to put them in either. So you'll need to put them in a script which runs
|
|
||||||
somewhere in the boot process somehow.
|
|
||||||
|
|
||||||
### Channels (ethtool -l)
|
|
||||||
|
|
||||||
The channels param configures how many CPU cores will communicate with the NIC.
|
|
||||||
You generally want this number to be equal to the number of CPU cores you have,
|
|
||||||
that way the load will be evenly spread across your CPU. If you have more CPU
|
|
||||||
cores than your NIC supports you can try turning multithreading off in the BIOS.
|
|
||||||
Or just accept that only a portion of your cores will communicate with the NIC,
|
|
||||||
it's not that big of a problem.
|
|
||||||
|
|
||||||
If you are running on a multi-CPU platform you only want one CPU to communicate
|
|
||||||
with the NIC. Distributing your channels over multiple CPUs will cause cache
|
|
||||||
thrashing which absolutely tanks performance. Many of pixeldrain's server are
|
|
||||||
dual CPU, where one CPU runs the pixeldrain software and the other only
|
|
||||||
communicates with the NIC. Buying a $10k CPU just to talk to a NIC is a bit
|
|
||||||
wasteful, I recommend just using one CPU if you have the choice.
|
|
||||||
|
|
||||||
Your NIC will usually configure the channels correctly on boot, so in most of
|
|
||||||
the cases you don't need to change anything here. You can query the settings
|
|
||||||
with `ethtool -l $INTERFACE` and update the values like this: `ethtool -L
|
|
||||||
$INTERFACE combined 63`.
|
|
||||||
|
|
||||||
### Ring buffers (ethtool -g)
|
|
||||||
|
|
||||||
The ring buffers are portions of RAM where the NIC stores your IP packets before
|
|
||||||
they are sent out to the network (tx) or sent to the CPU (rx). Increasing the
|
|
||||||
ring buffer sizes can increase network latency a little bit because more packets
|
|
||||||
are getting buffered before being sent out to the network. But again, at 100 GbE
|
|
||||||
this happens so fast that the difference is in the order of microseconds, that
|
|
||||||
makes absolutely no difference to us. We just want to move as much data as
|
|
||||||
possible in as little time as possible.
|
|
||||||
|
|
||||||
If we can buffer more packets then it means we can transfer more data in bulk
|
|
||||||
with every clock cycle. So we simply set this to the maximum. For Mellanox cards
|
|
||||||
the maximum is usually `8192`, but this can vary. Check the maximum values for
|
|
||||||
your card with `ethtool -g $INTERFACE`.
|
|
||||||
|
|
||||||
Set the receive and send buffers to 8192 packets: `ethtool -G $INTERFACE rx 8192
|
|
||||||
tx 8192`
|
|
||||||
|
|
||||||
### Interrupt Coalescing (ethtool -c)
|
|
||||||
|
|
||||||
The NIC can't just write your packets to the CPU and expect it to do something
|
|
||||||
with them. Your CPU needs to be made aware that there is new data to process.
|
|
||||||
That happens with an interrupt. Ethtool's interrupt coalescing values tell the
|
|
||||||
NIC when and how to send interrupts to the CPU. This is a delicate balance. We
|
|
||||||
don't want to interrupt the CPU too often, because then it won't be able to get
|
|
||||||
any work done. That's like getting a new ping in team chat every half hour, how
|
|
||||||
are you supposed to concentrate like that? But if we set the interrupt rate too
|
|
||||||
slow, the NIC won't be able to send all packets in time before the buffers fill
|
|
||||||
up.
|
|
||||||
|
|
||||||
The interrupt coalescing options vary a lot per NIC type.. These are the ones
|
|
||||||
which are present on my ConnectX-6 Dx: `rx-usecs`, `rx-frames`, `tx-usecs`,
|
|
||||||
`tx-frames`, `cqe-mode-rx`, `cqe-mode-tx`. I'll explain what these are:
|
|
||||||
|
|
||||||
* `rx-usecs`, `tx-usecs`: These values dictate how often the NIC interrupts the
|
|
||||||
CPU to receive packets `rx` or send packets `tx`. The value is in
|
|
||||||
microseconds. The SI prefix for micro is µ, but for convenience they use the
|
|
||||||
letter u here. A microsecond is one-millionth of a second.
|
|
||||||
* `rx-frames`, `tx-frames`: Like the values above this defines how often the
|
|
||||||
CPU is interrupted, but instead of interrupting the CPU at a fixed interval
|
|
||||||
it interrupts the CPU when a certain number of packets are in the buffer.
|
|
||||||
* `cqe-mode-rx`, `cqe-mode-tx`: These options enable packet compression in the
|
|
||||||
PCI bus. This is handy if your PCI bus is a bottleneck, like when your 100G
|
|
||||||
NIC is plugged into 4x PCI 4.0 lanes, which only has 7.88 GB/s bandwidth. In
|
|
||||||
most cases it's best to leave these at the default value.
|
|
||||||
* `adaptive-rx`, `adaptive-tx`: These values tell the NIC to calculate its own
|
|
||||||
interrupt timings. This disregards the values we configure ourselves. The
|
|
||||||
timings calculated by the NIC often prefer low latency over throughput and
|
|
||||||
can quickly overwhelm the CPU with interrupts. So for our purposes this needs
|
|
||||||
to be disabled.
|
|
||||||
|
|
||||||
So what are good values for these? Well, we can do some math here. Our NIC can
|
|
||||||
send 100 Gigabits per second. That's 12.5 GB. A network packet is usually 1500
|
|
||||||
bytes. This means that we need to send 8333333 packets per second to reach full
|
|
||||||
speed. Our ring buffer can hold 8192 packets, so if we divide by that number we
|
|
||||||
learn that we need to send 1017 entire ring buffers per second to reach full
|
|
||||||
speed.
|
|
||||||
|
|
||||||
Waiting for the ring buffer to be completely full is probably not a good idea,
|
|
||||||
since then we can't add more packets until the previous packets have been copied
|
|
||||||
out. So we want to be able to empty the ring buffer twice. That leaves us with
|
|
||||||
2034 ring buffers per second. Now convert that buffers per second number to µs
|
|
||||||
per buffer: `1000000 / 2034 = 492µs`, we land on a value of 492µs per interrupt.
|
|
||||||
This is our ceiling value. Higher than this and the buffers will overflow. But
|
|
||||||
492µs is nearly half a millisecond, that's an eternity in CPU time. That's high
|
|
||||||
enough that it might actually make a measurable difference in packet latency. So
|
|
||||||
we opt for a more sane value of 100µs instead. That still gives the CPU plenty
|
|
||||||
of time to do other work in between interrupts. A 3 GHz CPU core will be able to
|
|
||||||
perform about 30000 calculations inbetween each interrupt. At the same time it's
|
|
||||||
low enough to barely make a measurable difference in latency, at most a tenth of
|
|
||||||
a millisecond.
|
|
||||||
|
|
||||||
As for the `{rx,tx}-frames` variables. We just spent all that time calculating
|
|
||||||
the ideal interrupt interval, I don't really want the NIC to start interrupting
|
|
||||||
my CPU when it's not absolutely necessary. So we use the maximum ring buffer
|
|
||||||
value here: `8192`. Your NIC might not support such high coalescing values. You
|
|
||||||
can also try setting this to `4096` or `2048` if you notice problems.
|
|
||||||
|
|
||||||
That leaves us with this configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
ethtool -C $INTERFACE adaptive-rx off adaptive-tx off \
|
|
||||||
rx-usecs 100 tx-usecs 100 \
|
|
||||||
rx-frames 8192 tx-frames 8192
|
|
||||||
```
|
|
||||||
|
|
||||||
Tip: If you want to see how much time your CPU is spending on handling
|
|
||||||
interrupts, go into `htop`, then to Setup (F2) and enable "Detailed CPU time"
|
|
||||||
under Display options. The CPU gauge will now show time spent on handling
|
|
||||||
interrupts in purple. Press F10 to save changes.
|
|
||||||
|
|
||||||
## BIOS
|
|
||||||
|
|
||||||
Not even the BIOS is safe from our optimization journey. If fact, some of the
|
|
||||||
most important optimizations must be configured here.
|
|
||||||
|
|
||||||
### NUMA Nodes per socket
|
|
||||||
|
|
||||||
Big CPUs with lots of cores often segment their memory into NUMA nodes. These
|
|
||||||
smaller nodes get exclusive access to a certain portion of RAM and don't have to
|
|
||||||
contend over memory access with the other NUMA nodes. This can improve your
|
|
||||||
performance... if your software supports it well. But from my testing the setup
|
|
||||||
of one NIC queue per core does not combine well with having multiple NUMA nodes.
|
|
||||||
The fact that I use Go, which does not have a NUMA aware scheduler as far as I
|
|
||||||
know, probably does not help either. For these reasons I prefer to set `NUMA
|
|
||||||
nodes per socket` to `NPS1`.
|
|
||||||
|
|
||||||
Some AMD BIOSes also have an option called `ACPI SRAT L3 Cache as NUMA Domain`.
|
|
||||||
This will create NUMA nodes based on the L3 cache topology, *even if you
|
|
||||||
explicitly disabled NUMA in the memory addressing settings*. To fix this set
|
|
||||||
`ACPI SRAT L3 Cache as NUMA Domain` to `Disabled`.
|
|
||||||
|
|
||||||
### SMT Control
|
|
||||||
|
|
||||||
Multithreading (or Hyperthreading, on Intel) can be a performance booster, but
|
|
||||||
it can also be a performance bottleneck. If you have a CPU with a lot of cores,
|
|
||||||
like AMD's Epyc lineup, then disabling SMT can be a good way to improve per-core
|
|
||||||
performance.
|
|
||||||
|
|
||||||
Most apps have no way to effectively use hundreds of CPU threads. At some point
|
|
||||||
adding more threads will only consume more memory and CPU cycles just because
|
|
||||||
the kernel scheduler, memory controller and your language runtime have to manage
|
|
||||||
all those threads. This can cause huge amounts of overhead. My rule of thumb: If
|
|
||||||
you have 64 or more cores: `SMT OFF`
|
|
||||||
|
|
||||||
### IOMMU
|
|
||||||
|
|
||||||
The [Input-output memory management
|
|
||||||
unit](https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit)
|
|
||||||
is a CPU component for virtualizing your memory access. This can be useful if
|
|
||||||
you run a lot of VMs for example. You know what it's also good for? **Completely
|
|
||||||
destroying NIC performance**.
|
|
||||||
|
|
||||||
A high end NIC needs to shuffle a lot of data over the PCI bus. A 100 GbE NIC in
|
|
||||||
full duplex can reach up to 25 GB/s! When the IOMMU is enabled it means that all
|
|
||||||
the data that the NIC sends/receives needs to go through the IOMMU first before
|
|
||||||
it can go into RAM. This adds a little bit of latency. When you are running a
|
|
||||||
high end NIC in your PCI slot, then the added latency makes sure that your NIC
|
|
||||||
will **never ever get anywhwere near the advertised speed**. In some cases the
|
|
||||||
overhead is so large that the NIC will effectively drop off the PCI bus,
|
|
||||||
immediately crashing your system once it gets only slightly overloaded. Yes,
|
|
||||||
really, I have seen this happen.
|
|
||||||
|
|
||||||
Seriously, if you have a high end NIC plugged into your PCI slot and you have
|
|
||||||
the IOMMU enabled. **You might as well plug a goddamn brick into your PCI
|
|
||||||
slot**, because that's about as useful as your expensive NIC will be.
|
|
||||||
|
|
||||||
It took me way too long to find this information. The difference between IOMMU
|
|
||||||
off and on is night and day. I am actually **furious** that it took me so long
|
|
||||||
to discover this. I spent *weeks* pulling hair out of my head trying to figure
|
|
||||||
out why my NIC was locking up whenever I tried to put any real load on it. All
|
|
||||||
the NIC tuning guides I could find talk about tweaking little ethtool params,
|
|
||||||
installing drivers, updating firmware and useles crap like that, the IOMMU was
|
|
||||||
completely omitted in every one of them. I was getting so desperate with my
|
|
||||||
terrible NIC performance that I just started flipping toggles in the BIOS to see
|
|
||||||
if anything made a difference. If you have any idea how long it takes to reboot
|
|
||||||
a high end server system you know how tedious this is. That's how I discovered
|
|
||||||
that the IOMMU was the source of **all my problems**.
|
|
||||||
|
|
||||||
Ugh, just thinking about all the time I wasted because because nobody told me to
|
|
||||||
just turn the IOMMU off gets my blood boiling. That's why I am writing this
|
|
||||||
guide, I want to spare you the suffering.
|
|
||||||
|
|
||||||
So yea... `AMD CBS > NBIO Common Options > IOMMU > Disabled` ...AND STAY DOWN!
|
|
||||||
|
|
||||||
I also just turn off anything related to virtualization nowadays. Having
|
|
||||||
virtualization options enabled when you are not running VMs is a waste of
|
|
||||||
resources. No worries, docker is not virtualization, it's just namespacing,
|
|
||||||
nothing virtual about that. And if you are running VMs.. well, consider bare
|
|
||||||
metal. It's really not that scary and there is lots of performance to be gained.
|
|
||||||
|
|
||||||
You can verify that your IOMMU is disabled with this command `dmesg | grep
|
|
||||||
iommu`. Your IOMMU is disabled if it prints something along the lines of:
|
|
||||||
|
|
||||||
```
|
|
||||||
[ 1.302786] iommu: Default domain type: Translated
|
|
||||||
[ 1.302786] iommu: DMA domain TLB invalidation policy: lazy mode
|
|
||||||
```
|
|
||||||
|
|
||||||
If you see more output than that, you need to drop into the BIOS and nuke that
|
|
||||||
shit immediately.
|
|
||||||
|
|
||||||
One little caveat is that Linux requires the IOMMU to support more than 255 CPU
|
|
||||||
threads. So if you have 256 threads and the IOMMU is turned off one of your
|
|
||||||
threads will be disabled. So once again I will repeat my rule of thumb with
|
|
||||||
regards to multithreading: If you have 64 or more cores: `SMT OFF`
|
|
||||||
|
|
||||||
## Reverse proxy
|
|
||||||
|
|
||||||
A lot of sites run behind a reverse proxy like nginx or Caddy. It seems to be an
|
|
||||||
industry standard nowadays. People are surprised when they learn that pixeldrain
|
|
||||||
does not use one of the standard web servers.
|
|
||||||
|
|
||||||
As it turns out, 100 Gigabit per second is a lot of data. It takes a
|
|
||||||
considerable amount of CPU time to churn through that much data, so ideally you
|
|
||||||
want to touch it as few times as you can. At this scale playing hot potato with
|
|
||||||
your HTTP requests is a really bad idea.
|
|
||||||
|
|
||||||
A big bottleneck with networking on Linux is copying data across the kernel
|
|
||||||
boundary. The kernel always needs to copy your buffers because userspace is
|
|
||||||
dirty, ew, would not want to share memory with that. When you are running a
|
|
||||||
reverse proxy every HTTP request is effectively crossing the kernel boundary
|
|
||||||
*six times*. Let's assume we're running nginx here, the client sends a request
|
|
||||||
to the server. The kernel copies the request body from kernel space to nginx's
|
|
||||||
listener (from kernel space to userspace), nginx opens a request to your app and
|
|
||||||
copies the body the to localhost TCP socket (back to kernel space). The kernel
|
|
||||||
sends the body to your app's listener on localhost (now it's in userspace
|
|
||||||
again). And then the response body follows the same path again. Request: NIC ->
|
|
||||||
kernel -> userspace -> kernel -> userspace. Response: userspace -> kernel ->
|
|
||||||
userspace -> kernel -> NIC. That's crazy inefficient.
|
|
||||||
|
|
||||||
That's why pixeldrain just uses Go's built in HTTP server. Go's HTTP server is
|
|
||||||
very complete. Everything you need is there:
|
|
||||||
|
|
||||||
* [Routing](https://github.com/julienschmidt/httprouter)
|
|
||||||
* [TLS (for HTTPS)](https://pkg.go.dev/crypto/tls)
|
|
||||||
* HTTP/2
|
|
||||||
* Even a [reverse
|
|
||||||
proxy](https://pkg.go.dev/net/http/httputil#NewSingleHostReverseProxy) if
|
|
||||||
you're into that kinda stuff
|
|
||||||
|
|
||||||
The only requirement is that your app is written in Go. Of course other
|
|
||||||
languages also have libraries for this.
|
|
||||||
|
|
||||||
Zero-downtime restarts are a bit tricky. Luckily the geniuses tinkering away at
|
|
||||||
the Linux kernel every day made something neat for us. It's called
|
|
||||||
`SO_REUSEPORT` (Wow! Catchy name!). By putting this socket option on your TCP
|
|
||||||
listener you allow future instances of your server process to listen on the same
|
|
||||||
port at the same time. By doing this your upgrades become really quite simple:
|
|
||||||
|
|
||||||
1. Upload new server executable to the server.
|
|
||||||
2. Start the new executable up.
|
|
||||||
3. When everything is initialized it starts listening on the same port as the
|
|
||||||
previous process using `SO_REUSEPORT`.
|
|
||||||
4. After the listener is installed we signal to the old server process (which is
|
|
||||||
still running at this point) that it can start shutting down. The listener is
|
|
||||||
closed and the active HTTP requests are gracefully completed.
|
|
||||||
5. Once the old listener is closed all new requests will go to the new process
|
|
||||||
and the upgrade is complete.
|
|
||||||
|
|
||||||
Now there may be one question on your mind: How do I signal to the previous
|
|
||||||
process that the new process has finished initializing? I have just the thing
|
|
||||||
for you. [This handy-dandy library that I
|
|
||||||
made](https://github.com/Fornaxian/zerodown). I use it for pixeldrain and it
|
|
||||||
works like a charm. Your software updates are just one `SIGHUP` away from being
|
|
||||||
deployed.
|
|
||||||
|
|
||||||
## HTTP/2 or QUIC?
|
|
||||||
|
|
||||||
HTTP/2 and QUIC (HTTP/3) are new revisions of the HyperText Transfer Protocol.
|
|
||||||
HTTP/2 introduces multiplexing which significantly reduces handshake latency.
|
|
||||||
HTTP/1.1 will open a separate TCP session for each file it needs to request,
|
|
||||||
HTTP/2 opens one connection instead and uses framing to send multiple requests
|
|
||||||
at the same time instead, this allows the connection to ramp up to a higher
|
|
||||||
speed and quicker. This goes hand in hand with the BBR congestion control
|
|
||||||
algorithm which also significantly reduces connection ramp-up time. The result
|
|
||||||
is 60% faster loading times for web pages on average.
|
|
||||||
|
|
||||||
HTTP/2 is trivially enabled in the Go HTTP server. Simply add `NextProtos =
|
|
||||||
[]string{"h2"}` to your `tls.Config` and it's good to go. An annoying
|
|
||||||
implementation detail is that Go's HTTP/2 server throws completely different
|
|
||||||
errors than HTTP/1.1, so you will have to redo all your error handling. To make
|
|
||||||
matters worse, HTTP/2's errors are not exported by the `http` package, so you
|
|
||||||
have to resort to string searching to catch these errors.. 😒.
|
|
||||||
|
|
||||||
Then along comes HTTP/3, also known as QUIC. HTTP/3 throws everything we just
|
|
||||||
did out of the window and uses UDP instead. It moves all the buffer management
|
|
||||||
and congestion control to userspace. Sure, you get more control that way, but
|
|
||||||
that's really only useful if you're Google. I tried the most popular HTTP/3
|
|
||||||
server implementation for Go, and it struggled to even reach half of the
|
|
||||||
throughput I got with HTTP/2. Sure, latency is lower, but that's not that useful
|
|
||||||
to me when the most important part of my site stops functioning. Sure, TCP is
|
|
||||||
not perfect, but it's better than having to do everything yourself.
|
|
||||||
|
|
||||||
To summarize, if you only care about throughput: HTTP/2 👍 HTTP/3 👎 (for now)
|
|
||||||
|
|
||||||
## Operating system
|
|
||||||
|
|
||||||
Choose something up-to-date, lightweight and minimalist. Pixeldrain used to run
|
|
||||||
on Ubuntu because I was familiar with it, but over time Ubuntu server got more
|
|
||||||
bloated and heavy. Unnecessary stuff was being added with each new release
|
|
||||||
(looking at you snapd), and I just didn't want to deal with that. Eventually I
|
|
||||||
switched to Debian.
|
|
||||||
|
|
||||||
Debian is so much better than Ubuntu. After booting it for the first time there
|
|
||||||
will only be like 10 processes running on the system, just the essentials. It
|
|
||||||
really is a clean sandbox waiting for you to build a castle in it. It might take
|
|
||||||
some getting used to, but it will definitely pay off.
|
|
||||||
|
|
||||||
Anyway, that's just my opinion. In reality you can pick any distro you like. It
|
|
||||||
does not really matter that much. Just keep in mind that some distro's ship
|
|
||||||
newer kernels than others, and that's really quite important as we will learn in
|
|
||||||
the next paragraph.
|
|
||||||
|
|
||||||
## Kernel
|
|
||||||
|
|
||||||
You need to run at least kernel 6.1, because of the `net.ipv4.tcp_shrink_window`
|
|
||||||
sysctl. But generally, **newer is better**. There are dozens of engineers from
|
|
||||||
Google, Cloudflare and Meta tinkering away at the Linux network stack every day.
|
|
||||||
It gets better with every release, really, the pace is staggering.
|
|
||||||
|
|
||||||
But doesn't Debian ship quite old kernel packages? (you might ask) Yes... kinda.
|
|
||||||
By using [this guide](https://wiki.debian.org/HowToUpgradeKernel) you can
|
|
||||||
upgrade your kernel version to the `testing` or even the `experimental` branch
|
|
||||||
while keeping the rest of the OS the same.
|
|
||||||
|
|
||||||
On the [Debian package tracker](https://tracker.debian.org/pkg/linux) you can
|
|
||||||
see which kernel version ships in which repository. This is useful for picking
|
|
||||||
which repo you want to use for your kernel updates. Pixeldrain gets its kernel
|
|
||||||
updates from the `testing` branch. These are kernels which have been declared
|
|
||||||
stable by the kernel developers and are generally safe to use.
|
|
||||||
|
|
||||||
Keep an eye on the [Phoronix Linux Networking
|
|
||||||
blog](https://www.phoronix.com/linux/Linux+Networking) for new kernel features.
|
|
||||||
Pretty much every kernel version that comes out boasts about huge network
|
|
||||||
performance wins. I'm personally waiting for Kernel 6.8 to come out. They are
|
|
||||||
promising a 40% TCP performance boost. Crazy!
|
|
||||||
|
|
||||||
## That's all, folks!
|
|
||||||
|
|
||||||
**Behold.. One hundred gigabits per second!**
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Actually my nload seems to cap out at around 87 Gbps.. there's probably some
|
|
||||||
overhead somewhere. It's close though.
|
|
||||||
|
|
||||||
I hope this guide was useful to you. I wish I had something like this when I
|
|
||||||
started out. I could have quite literally saved me months of time. Then again,
|
|
||||||
chasing 100 Gigabit is one of the most educative challenges I have ever faced. I
|
|
||||||
have learned so much about Linux's structure, kernel performance profiling, CPU
|
|
||||||
architecture, the PCIe bus and tons of other things that I would never have
|
|
||||||
known if I did not go down this rabbit hole. And I have a feeling the journey is
|
|
||||||
not over. I will always have this urge to get the absolute most out of my
|
|
||||||
servers. I'm paying for the whole CPU and I'm going to use the whole CPU after
|
|
||||||
all.
|
|
||||||
|
|
||||||
Anyway, check out [Pixeldrain](/) if you like, it's the fastest way to transfer
|
|
||||||
files across the web. And I'm working on a [cloud storage](/filesystem) offering
|
|
||||||
as well. It has built in rclone and FTPS support. Pixeldrain also has a built in
|
|
||||||
[speedtest](/speedtest) which you can use to see the fruits of my labour. The
|
|
||||||
source for this document is available in markdown format on [my
|
|
||||||
GitHub](https://github.com/Fornaxian/pixeldrain_web/blob/master/res/include/md/100_gigabit_ethernet.md).
|
|
||||||
|
|
||||||
Follow me on [Mastodon](https://mastodon.social/@fornax),
|
|
||||||
[Twitter](https://twitter.com/Fornax96), join our
|
|
||||||
[Discord](https://discord.gg/TWKGvYAFvX), et cetera et cetera
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
# Questions and Answers
|
|
||||||
|
|
||||||
[TOC]
|
|
||||||
|
|
||||||
## For how long will my files be stored?
|
|
||||||
|
|
||||||
Files will be removed if they have not been accessed for 120 days. When a file
|
|
||||||
is downloaded the expiry time is reset to 120 days from the current day. This
|
|
||||||
only happens when someone downloads more than 10% of the whole file in a single
|
|
||||||
request. So if you have a 5 GB file the timer is only extended when you download
|
|
||||||
at least 500 MB. The expiry timer is not updated when it was already updated
|
|
||||||
within the last 24 hours.
|
|
||||||
|
|
||||||
File expiry is often seen as a downside of pixeldrain. But keep in mind that 120
|
|
||||||
days is a very long time. Roughly four months. This means that you can keep a
|
|
||||||
file active for an entire year by only downloading it three times. Files which
|
|
||||||
are only very sporadically downloaded can stay online indefinitely. All this
|
|
||||||
time the file is using storage space and processing power on our servers, which
|
|
||||||
costs real money.
|
|
||||||
|
|
||||||
If you would like to use pixeldrain for backups or long term storage, then the
|
|
||||||
free plan is _not the way to go_. It is only meant for publicly sharing files.
|
|
||||||
For real storage you should use the [pixeldrain filesystem](/filesystem).
|
|
||||||
|
|
||||||
If you are (ab)using pixeldrain for free storage, you should not be surprised
|
|
||||||
when the rules change at some point and all your stuff suddenly disappears. That
|
|
||||||
would never happen when using pixeldrain's filesystem.
|
|
||||||
|
|
||||||
## What cookies does pixeldrain use?
|
|
||||||
|
|
||||||
When logging in to a pixeldrain account a cookie named 'pd_auth_key' will be
|
|
||||||
installed. This cookie keeps your login session active. When you delete it you
|
|
||||||
will be logged out of your account.
|
|
||||||
|
|
||||||
When you use the style selector on the [Appearance](/appearance) page a cookie
|
|
||||||
called 'style' will be set. This cookie controls the appearance of the website
|
|
||||||
for you.
|
|
||||||
|
|
||||||
Pixeldrain does not use tracking cookies. We also don't use fingerprinting to
|
|
||||||
track our users. The only information that is saved is the information that you
|
|
||||||
manually enter or upload.
|
|
||||||
|
|
||||||
## How does the transfer limit work?
|
|
||||||
|
|
||||||
Pixeldrain has two kinds of transfer limit, the free limit for users without a
|
|
||||||
subscription and the premium limit for users with a subscription.
|
|
||||||
|
|
||||||
### Free
|
|
||||||
|
|
||||||
The free limit tracks how much you have downloaded from pixeldrain in the last
|
|
||||||
24 hours from now. The limit is _not per day_, instead it just keeps track of
|
|
||||||
when you downloaded something and if it was less than 24 hours ago it counts
|
|
||||||
towards your limit. In technical terms this is called a 'Sliding window
|
|
||||||
algorithm'.
|
|
||||||
|
|
||||||
The free download limit is only tracked per _IP address_. This means that if you
|
|
||||||
are sharing an IP address with other people, like through a VPN, company network
|
|
||||||
or a CGNAT network then the download limit is also shared. For free downloads it
|
|
||||||
makes no difference if you're logged in to an account or not.
|
|
||||||
|
|
||||||
When the limit is exceeded you can still download, but file previews are
|
|
||||||
disabled and download speed is reduced.
|
|
||||||
|
|
||||||
### Premium
|
|
||||||
|
|
||||||
The premium transfer limit works similarly to the free download limit. The
|
|
||||||
differences are that the limit is bound to an account instead of an IP address,
|
|
||||||
and the limit is per 30 days instead of 24 hours. The same sliding window system
|
|
||||||
still applies. Any data that was transferred to/from your account between now
|
|
||||||
and 30 days ago counts towards your limit.
|
|
||||||
|
|
||||||
Whenever someone downloads a file from your account it counts toward your
|
|
||||||
transfer limit. If you want to limit how much of your transfer cap others can
|
|
||||||
use then you can configure a limit on the [sharing settings
|
|
||||||
page](/user/sharing).
|
|
||||||
|
|
||||||
If the person who downloads the file also has a premium account then their own
|
|
||||||
data cap will be used first.
|
|
||||||
|
|
||||||
## How does hotlinking work?
|
|
||||||
|
|
||||||
Hotlinking happens when someone downloads a file from pixeldrain without
|
|
||||||
visiting the pixeldrain website. This can be through embedding media files on
|
|
||||||
third party websites, or using download managers to download files directly.
|
|
||||||
|
|
||||||
Pixeldrain has a "hotlink protection mode". This activates when we detect that a
|
|
||||||
file is being hotlinked while neither the downloader nor the uploader of the
|
|
||||||
file has a premium subscription. When this happens a CAPTCHA test will appear on
|
|
||||||
the file's download page, and the file can only be downloaded once the CAPTCHA
|
|
||||||
is solved. When enough people complete the test the hotlink protection will be
|
|
||||||
removed and the file can be downloaded normally again.
|
|
||||||
|
|
||||||
There are two reasons why we implemented hotlink protection:
|
|
||||||
|
|
||||||
File hosting services are often used to spread malware and other nefarious data,
|
|
||||||
hotlink protection makes it significantly harder for people to abuse the service
|
|
||||||
in this way. This was the original motivation for implementing hotlink
|
|
||||||
protection, it has been very effective at preventing digital attacks.
|
|
||||||
|
|
||||||
Hotlinking also uses pixeldrain's bandwidth and processing power without letting
|
|
||||||
the user know that they are using pixeldrain. People who don't know that they
|
|
||||||
are using pixeldrain are less likely to purchase a premium plan. The download
|
|
||||||
page is our primary source of new customers, we need to make sure it is seen.
|
|
||||||
|
|
||||||
## Will premium improve my download speed?
|
|
||||||
|
|
||||||
No, the download speed is limited by the stability of the connection between
|
|
||||||
your computer and pixeldrain's servers. If free downloads are slow (and you have
|
|
||||||
not exceeded your download limit), then premium will not improve your download
|
|
||||||
speed. Premium only increases how much you can download, not how fast.
|
|
||||||
|
|
||||||
If you want to know your maximum download speed from pixeldrain's servers you
|
|
||||||
can use our [speedtest](/speedtest). The speedtest will always download at the
|
|
||||||
fastest speed possible, even if your download limit has been exceeded.
|
|
||||||
|
|
||||||
In order to keep pixeldrain affordable we use the cheapest hosting available.
|
|
||||||
That means that the quality of our network is not always the best. It's possible
|
|
||||||
that your ISP has a bad connection to our ISP which can cause bottlenecks. We
|
|
||||||
are always working on improving our connectivity.
|
|
||||||
|
|
||||||
## Is pixeldrain available in every country?
|
|
||||||
|
|
||||||
I strive to make pixeldrain as accessible as possible to everyone. Pixeldrain
|
|
||||||
does not block access from any country or network. Some countries have very
|
|
||||||
restricted internet connectivity though. Pixeldrain has been blocked in some
|
|
||||||
locations in the past and remains blocked in other locations.
|
|
||||||
|
|
||||||
The reasons for blocking pixeldrain are usually not clear. The countries that
|
|
||||||
block access to pixeldrain rarely specify a reason. When a new block happens I
|
|
||||||
always reach out to the ISP or government doing the blocking, but these entities
|
|
||||||
are very hard to reach and they rarely reply. If your ISP blocks pixeldrain
|
|
||||||
**please call them and ask them why pixeldrain is blocked**. ISPs always listen
|
|
||||||
better to their own customers than website operators.
|
|
||||||
|
|
||||||
I have a survey that you can use to notify me when an ISP blocks access to
|
|
||||||
pixeldrain. If you are having trouble accessing pixeldrain please [fill out the
|
|
||||||
survey](https://forms.gle/jThCp5S6xi49w2KP7).
|
|
||||||
|
|
||||||
Usually a website block can be circumvented by using a different DNS provider.
|
|
||||||
The DNS provider is a service that translates website addresses (like
|
|
||||||
pixeldrain.com) into IP addresses that can be used to connect to a website.
|
|
||||||
These servers are usually operated by your ISP and can be used to censor or
|
|
||||||
monitor your browsing.
|
|
||||||
|
|
||||||
Pixeldrain also has alternative domain names which might not be blocked. These
|
|
||||||
are [pixeldrain.net](https://pixeldrain.net) and
|
|
||||||
[pixeldra.in](https://pixeldra.in). Note that your session cookie is only valid
|
|
||||||
for one domain name. If you use these alternative domains you will have to log
|
|
||||||
in to them as well.
|
|
||||||
|
|
||||||
### DNS Providers which don't block pixeldrain
|
|
||||||
|
|
||||||
You can find a guide for how to change your DNS server on Google. Just search
|
|
||||||
for 'change dns server windows 11', or whichever operating system you use.
|
|
||||||
|
|
||||||
| Provider | IPv4 addreses | IPv6 addresses |
|
|
||||||
|------------|--------------------------|--------------------------------------------|
|
|
||||||
| Cloudflare | 1.1.1.1, 1.0.0.1 | 2606:4700:4700::1111, 2606:4700:4700::1001 |
|
|
||||||
| Quad9 | 9.9.9.9, 149.112.112.112 | 2620:fe::fe, 2620:fe::9 |
|
|
||||||
| Google | 8.8.8.8, 8.8.4.4 | 2001:4860:4860::8888, 2001:4860:4860::8844 |
|
|
||||||
|
|
||||||
### Countries where pixeldrain is blocked
|
|
||||||
|
|
||||||
From the availability survey I have gathered that pixeldrain is currently
|
|
||||||
blocked in the following locations:
|
|
||||||
|
|
||||||
* The Philippines (since 2022-03). I have reached out to PLDT about a dozen
|
|
||||||
times but they never answer.
|
|
||||||
* Egypt (since 2023-03). I have tried reaching out to WE Telecom, but their
|
|
||||||
website is not available outside egypt and their support address is bouncing
|
|
||||||
my mails.
|
|
||||||
* Italy (since 2024-01). Tried reaching out to their communications office and
|
|
||||||
police multiple times, never an answer.
|
|
||||||
* India (since 2024-04). Was unable to find a contact address anywhere.
|
|
||||||
|
|
||||||
If you live in any of these locations and are having trouble accessing
|
|
||||||
pixeldrain **please contact your ISP**. I am ready to comply with whatever
|
|
||||||
demands they have, I just want my website to be accessible again.
|
|
||||||
|
|
||||||
### Statement regarding the Italy block
|
|
||||||
|
|
||||||
I have been working on the blocking situation in Italy, but there has been no
|
|
||||||
progress. I have contacted every phone number or e-mail address I could find.
|
|
||||||
All my e-mails are ignored and half of the phone numbers are out of service and
|
|
||||||
the other half connect to (very impatient) people who don't speak english.
|
|
||||||
|
|
||||||
I do not know what else I can do to make more progress on this situation. I have
|
|
||||||
exhausted my options. Is there anyone in Italy or who speaks Italian who would
|
|
||||||
be willing to help? I need to get in contact with the Polizia Postale, which has
|
|
||||||
been blocking access to pixeldrain.com nationwide for well over a year now.
|
|
||||||
|
|
||||||
Not only are they blocking access to the site (which is bad enough), they are
|
|
||||||
also spreading a terrifying and misleading image of what pixeldrain is being
|
|
||||||
used for. This is seriously affecting the reputation of my business and myself.
|
|
||||||
I'd really like to get this resolved. I am also willing to offer a financial
|
|
||||||
compensation for anyone who can help me get this resolved. If you think you can
|
|
||||||
help, please contact me [on Discord](https://discord.gg/UDjaBGwr4p). My username
|
|
||||||
is Fornax.
|
|
||||||
|
|
||||||
## Why can't I find pixeldrain links on Google?
|
|
||||||
|
|
||||||
Files on pixeldrain used to be searchable with search engines if they were
|
|
||||||
indexed. People often accidentally got files indexed which were not supposed to
|
|
||||||
be public. For that reason I disabled search indexing on all pixeldrain files.
|
|
||||||
This protects the privacy of pixeldrain users and helps with preventing
|
|
||||||
information leaks.
|
|
||||||
|
|
||||||
## How does the affiliate program work?
|
|
||||||
|
|
||||||
Pixeldrain's affiliate program is a way to earn pixeldrain credit by driving
|
|
||||||
traffic to pixeldrain. The way it works is that you send people your affiliate
|
|
||||||
link, if someone accepts to be your affiliate then their active subscription
|
|
||||||
will earn you pixeldrain credit. The affiliate program is opt-in and fully
|
|
||||||
transparent. Users will always be notified when their affiliate account is
|
|
||||||
updated. You can update who you are sponsoring by editing the affiliate name on
|
|
||||||
your [user settings page](/user/settings).
|
|
||||||
|
|
||||||
Here is a summary of all the rules and limitations:
|
|
||||||
|
|
||||||
* Each paying customer using your affiliate code will earn you €0.50 in prepaid
|
|
||||||
credit every month. The credit is added to your account on a daily basis, as
|
|
||||||
you can see on the [transactions page](/user/prepaid/transactions).
|
|
||||||
* Sponsoring someone with an affiliate code does not cost you any extra money.
|
|
||||||
The resulting fee comes out of pixeldrain's pockets.
|
|
||||||
* You can only earn pixeldrain credit with the affiliate program. There is no
|
|
||||||
cash out feature.
|
|
||||||
* When someone who is using your affiliate code cancels their plan, you will
|
|
||||||
also stop receiving rewards.
|
|
||||||
* You don't need an active subscription to gain credit through the affiliate
|
|
||||||
program. You need a positive balance of at least €1 to activate the prepaid
|
|
||||||
plan.
|
|
||||||
|
|
||||||
Some fun facts:
|
|
||||||
|
|
||||||
* You only need two affiliates to offset pixeldrain's base subscription fee.
|
|
||||||
* Each sponsoring user is effectively equal to 125 GB of storage space or 500 GB
|
|
||||||
of bandwidth usage per month.
|
|
||||||
* You cannot sponsor yourself.
|
|
||||||
|
|
||||||
## Is there a clean pixeldrain logo I can use?
|
|
||||||
|
|
||||||
Yes, here's a high resolution pixeldrain logo with text. The font is called
|
|
||||||
Orbitron, it was designed by Matt McInerney and uses the Open Font License.
|
|
||||||
|
|
||||||
<img src="/res/img/pixeldrain_high_res.png" style="max-width: 100%; height: 80px;" /><br/>
|
|
||||||
|
|
||||||
And here's a vector version of just the icon:
|
|
||||||
|
|
||||||
<img src="/res/img/pixeldrain.svg" style="max-width: 100%; height: 80px;" /><br/>
|
|
||||||
|
|
||||||
## Can I advertise on pixeldrain?
|
|
||||||
|
|
||||||
No.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
If you have more questions please try asking them in our [support forum on
|
|
||||||
Discord](https://discord.gg/UDjaBGwr4p). Pixeldrain is a one-man operation, I
|
|
||||||
can't answer all the e-mails I get. By asking your questions on Discord there's
|
|
||||||
a chance that someone else can help you. I am also active on Discord myself.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# Content policy
|
|
||||||
|
|
||||||
[TOC]
|
|
||||||
|
|
||||||
## Disallowed contents
|
|
||||||
|
|
||||||
The following types of content are not allowed to be shared on pixeldrain. They
|
|
||||||
will be removed when reported.
|
|
||||||
|
|
||||||
* **Copyright violation** - Works which are shared without permission from the
|
|
||||||
copyright holder. For copyright reports we need a formal DMCA takedown request
|
|
||||||
originating from the copyright holder or a representative. See the chapter
|
|
||||||
[E-Mail reporting rules](#toc_2) below. When sending a copyright infringement
|
|
||||||
notice to our abuse address, please state clearly that it is a copyright
|
|
||||||
infringement notice so that we can properly detect the type of report. Using
|
|
||||||
words like "theft" or "stolen" won't be detected because they are too vague.
|
|
||||||
|
|
||||||
* **Abuse of minors** - Videos, images or audio fragments depicting abuse or
|
|
||||||
inappropriate touching of minors will be removed. Users uploading this type of
|
|
||||||
content will receive a permanent account and IP address ban.
|
|
||||||
|
|
||||||
* **Zoophilia** - Videos, images or audio depicting abuse or inappropriate
|
|
||||||
touching of animals.
|
|
||||||
|
|
||||||
* **Terrorism** - Videos, images or audio fragments which promote and glorify
|
|
||||||
acts of terrorism. This category is only for material which promotes
|
|
||||||
terrorism. Material which shows terrorism (torture, murder) should be reported
|
|
||||||
under the _gore_ category.
|
|
||||||
|
|
||||||
* **Gore** - Graphic and shocking videos or images depicting severe harm to
|
|
||||||
humans (or animals).
|
|
||||||
|
|
||||||
* **Malware and computer viruses** - Software programs designed to cause harm to
|
|
||||||
computer systems. Please attach some proof that the file actually contains
|
|
||||||
malware, like a VirusTotal scan result. If no proof is provided then we will
|
|
||||||
assume the report is invalid.
|
|
||||||
|
|
||||||
* **Doxing** - Publishing private information about individuals or
|
|
||||||
organisations. This includes publicly sharing address information, ID scans or
|
|
||||||
login credentials which are not supposed to be public. Please [read about what
|
|
||||||
doxing really means](https://en.wikipedia.org/wiki/Doxing) before filing a
|
|
||||||
report in this category. If the file is content that you produce and sell,
|
|
||||||
then it does not fall under the doxing category. In that case you should file
|
|
||||||
a copyright claim.
|
|
||||||
|
|
||||||
* **Revenge porn** - The distribution of sexually explicit images or videos of
|
|
||||||
individuals without their consent. This category _does not apply_ for material
|
|
||||||
which is sold online for money. In that case you should file a copyright
|
|
||||||
claim. Please [read about what revenge porn really
|
|
||||||
means](https://en.wikipedia.org/wiki/Revenge_porn) before filing a report in
|
|
||||||
this category.
|
|
||||||
|
|
||||||
Violating these rules will result in your IP address being banned from uploading
|
|
||||||
to pixeldrain.
|
|
||||||
|
|
||||||
If you have found content which falls in any of these categories on pixeldrain
|
|
||||||
please report it by sending an e-mail to
|
|
||||||
[abuse@pixeldrain.com](mailto:abuse@pixeldrain.com). Please state clearly in
|
|
||||||
which category the content falls, and don't forget to include a link to the
|
|
||||||
content itself in the e-mail. When reporting links through e-mail pay attention
|
|
||||||
to the rules described below.
|
|
||||||
|
|
||||||
## E-Mail reporting rules
|
|
||||||
|
|
||||||
Pixeldrain's abuse handling process has been largely automated. Messages sent to
|
|
||||||
[abuse@pixeldrain.com](mailto:abuse@pixeldrain.com) are automatically scanned
|
|
||||||
for pixeldrain links and processed. The first report we receive from a sender is
|
|
||||||
manually reviewed. If the report is approved then your e-mail address will be
|
|
||||||
added to our whitelist and all following messages are processed automatically.
|
|
||||||
For this to work efficiently we have to set some requirements on the mails we
|
|
||||||
receive:
|
|
||||||
|
|
||||||
* Messages are categorized based on their contents. Make sure the report
|
|
||||||
contains a description of the type of content and that it mentions one of the
|
|
||||||
abuse categories listed above. The abuse mailbox only accepts reports written
|
|
||||||
in English.
|
|
||||||
|
|
||||||
* Do not add attachments to your e-mail reports. Only the e-mail body is checked
|
|
||||||
for download links. The message scanning system will not check your
|
|
||||||
attachments, download links within the attached files are not detected.
|
|
||||||
|
|
||||||
* Do not obfuscate the pixeldrain links. The reported download links need to be
|
|
||||||
complete and valid.
|
|
||||||
|
|
||||||
* The e-mail must include a
|
|
||||||
[Message-ID](https://en.wikipedia.org/wiki/Message-ID) header. The Message-ID
|
|
||||||
is used to reference messages in our system, mails without a Message-ID are
|
|
||||||
not processed.
|
|
||||||
|
|
||||||
* The abuse system uses e-mail addresses for authentication so we need to be
|
|
||||||
wary of [message spoofing](https://en.wikipedia.org/wiki/Email_spoofing). To
|
|
||||||
combat this we require both
|
|
||||||
[DKIM](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) and
|
|
||||||
[SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework) validation before
|
|
||||||
we can accept e-mails. If either of these checks fail we assume the message
|
|
||||||
was spoofed and it goes straight to the spambox. If your message is vulnerable
|
|
||||||
to spoofing then the bot will not reply to your message because we don't want
|
|
||||||
to send e-mails to spoofed addresses.
|
|
||||||
|
|
||||||
* Only send abuse reports to
|
|
||||||
[abuse@pixeldrain.com](mailto:abuse@pixeldrain.com). Messages sent to any
|
|
||||||
other e-mail address are ignored.
|
|
||||||
|
|
||||||
* Do not repeatedly send reports about files which have already been removed in
|
|
||||||
the past. We will block your e-mail address if this happens.
|
|
||||||
|
|
||||||
If you are not sure if your mailserver is configured correctly, then you can try
|
|
||||||
the spam test at [mail-tester.com](https://www.mail-tester.com/). Send an e-mail
|
|
||||||
to the address listed on this site and it will tell you if your mailserver is
|
|
||||||
configured right. Pay attention to the SPF, DKIM and DMARC results.
|
|
||||||
|
|
||||||
If your abuse report is rejected for one of the above reasons then you will
|
|
||||||
receive a reply with instructions on how to fix it.
|
|
||||||
|
|
||||||
## Police requests
|
|
||||||
|
|
||||||
In the case of official police investigations, pixeldrain can share user
|
|
||||||
details when requested. Please send all police data requests to
|
|
||||||
[police@pixeldrain.com](mailto:police@pixeldrain.com).
|
|
||||||
|
|
||||||
The following types of data are tracked in the pixeldrain database and can be
|
|
||||||
shared depending on the type of the case. Please specify which fields you
|
|
||||||
require in the investigation:
|
|
||||||
|
|
||||||
* File upload date and time (also publicly available on the download page)
|
|
||||||
* File upload IP address
|
|
||||||
* Username of the user who uploaded the file
|
|
||||||
* E-mail address of user
|
|
||||||
* Active login sessions of user
|
|
||||||
* User-agent of user
|
|
||||||
* IP addresses of active sessions of user
|
|
||||||
* Registration date of user
|
|
||||||
|
|
||||||
The severity and sensitivity of the request will be chosen at our own
|
|
||||||
discretion. In cases where the request is considered not serious or unlawful, we
|
|
||||||
reserve the right to deny the request for information.
|
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
|
|
||||||
Fornaxian Technologies cannot be held liable for any illegal or copyrighted
|
|
||||||
material that's uploaded by the users of this application under the Online
|
|
||||||
Copyright Infringement Liability Limitation Act § 512\(c) in the USA and the
|
|
||||||
Electronic Commerce Directive 2000 Article 14 in the EU.
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Acknowledgements
|
|
||||||
|
|
||||||
## Software used
|
|
||||||
|
|
||||||
* [Go](https://golang.org/)
|
|
||||||
* [ScyllaDB](https://www.scylladb.com/)
|
|
||||||
* [Nginx](https://www.nginx.com/)
|
|
||||||
* [Ubuntu Server edition](https://ubuntu.com/)
|
|
||||||
* [Debian](https://www.debian.org/)
|
|
||||||
|
|
||||||
## Programming libraries
|
|
||||||
|
|
||||||
* [scylladb/gocql](https://github.com/scylladb/gocql) (database communication)
|
|
||||||
* [scylladb/gocqlx](https://github.com/scylladb/gocqlx) (database communication)
|
|
||||||
* [BurntSushi/toml](https://github.com/BurntSushi/toml) (server configuration)
|
|
||||||
* [klauspost/reedsolomon](https://github.com/klauspost/reedsolomon) (reed-solomon erasure coding)
|
|
||||||
* [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter) (request routing)
|
|
||||||
* [gabriel-vasile/mimetype](https://github.com/gabriel-vasile/mimetype) (MIME type detection)
|
|
||||||
* [disintegration/imaging](https://github.com/disintegration/imaging) (image thumbnail generation)
|
|
||||||
* [ffmpeg](https://ffmpeg.org/) (video thumbnail generation)
|
|
||||||
* [gorilla/websocket](https://github.com/gorilla/websocket) (websocket support on file viewer)
|
|
||||||
* [shopspring/decimal](https://github.com/shopspring/decimal) (currency handling)
|
|
||||||
* [jhillyerd/enmime](https://github.com/jhillyerd/enmime) (e-mail parser)
|
|
||||||
* [russross/blackfriday](https://github.com/russross/blackfriday) (markdown renderer)
|
|
||||||
* [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) (HTML sanitizer)
|
|
||||||
* [j-muller/go-torrent-parser](https://github.com/j-muller/go-torrent-parser) (torrent file parser)
|
|
||||||
|
|
||||||
### Web framework
|
|
||||||
|
|
||||||
* [Svelte](https://svelte.dev/)
|
|
||||||
|
|
||||||
## Other resources
|
|
||||||
|
|
||||||
* [The map image](/res/img/map.webp) on the home page is from [Wikimedia
|
|
||||||
Commons](https://en.wikipedia.org/wiki/File:A_large_blank_world_map_with_oceans_marked_in_blue.PNG)
|
|
||||||
and is licensed under the GNU Free Documentation License.
|
|
||||||
* [The background image](/res/img/inflating_star.webp) on the home page is by
|
|
||||||
[NASA Goddard](https://images.nasa.gov/details-GSFC_20171208_Archive_e000383)
|
|
||||||
and is [allowed to be used
|
|
||||||
commercially](https://www.nasa.gov/nasa-brand-center/images-and-media/).
|
|
||||||
|
|
||||||
## Security work
|
|
||||||
|
|
||||||
* 2020-12-06 Security researcher Arian Firoozfar reported a cross-site
|
|
||||||
scripting vulnerability on the file viewer page. The issue was fixed the
|
|
||||||
following day. On the 26th another XSS vulnerability was found on the account
|
|
||||||
settings page, it was fixed later that day.
|
|
||||||
|
|
||||||
* 2017-12-04 Security researcher Hangyi reported a cross-site scripting
|
|
||||||
vulnerability on the file viewer page. The issue was fixed on the 6th.
|
|
||||||
|
|
||||||
If you have discovered a security issue in pixeldrain please disclose it
|
|
||||||
responsibly at [support@pixeldrain.com](mailto:support@pixeldrain.com). We do
|
|
||||||
not have a bug bounty program.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# API documentation
|
# API documentation
|
||||||
|
|
||||||
Methods for using pixeldrain programmatically.
|
Methods for using Nova programmatically.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ Example usage in JavaScript:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
"https://pixeldrain.com/api/user/files",
|
"https://nova.storage/api/user/files",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": "Basic "+btoa(":"+api_key),
|
"Authorization": "Basic "+btoa(":"+api_key),
|
||||||
// The btoa function encodes the key to Base64
|
// The btoa function encodes the key to Base64
|
||||||
@@ -39,20 +39,20 @@ URL.
|
|||||||
|
|
||||||
## curl example
|
## curl example
|
||||||
|
|
||||||
To upload files to pixeldrain you will need an API key. Get an API key from the
|
To upload files to Nova you will need an API key. Get an API key from the
|
||||||
[API keys page](/user/api_keys) and enter it in the command. Replace the example
|
[API keys page](/user/api_keys) and enter it in the command. Replace the example
|
||||||
API key here with your own:
|
API key here with your own:
|
||||||
|
|
||||||
`curl -T "file_name.txt" -u :5f45f184-64bb-4eaa-be19-4a5f0459db49
|
`curl -T "file_name.txt" -u :5f45f184-64bb-4eaa-be19-4a5f0459db49
|
||||||
https://pixeldrain.com/api/file/`
|
https://nova.storage/api/file/`
|
||||||
|
|
||||||
## Form value order
|
## Form value order
|
||||||
|
|
||||||
I recommend you put files at the end of every file upload form. By doing this
|
I recommend you put files at the end of every file upload form. By doing this
|
||||||
the pixeldrain server can respond to malformed requests before the file upload
|
the Nova server can respond to malformed requests before the file upload
|
||||||
finishes and this may save you a lot of time and bandwidth when uploading large
|
finishes and this may save you a lot of time and bandwidth when uploading large
|
||||||
files. Make sure your HTTP client has support for premature responses,
|
files. Make sure your HTTP client has support for premature responses,
|
||||||
pixeldrain uses them a lot. If the server responds before your request is
|
Nova uses them a lot. If the server responds before your request is
|
||||||
finished it will always indicate an error and you may abort the connection.
|
finished it will always indicate an error and you may abort the connection.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -161,11 +161,11 @@ Warning: If a file is using too much bandwidth it can be rate limited. The rate
|
|||||||
limit will be enabled if a file has three times more downloads than views. The
|
limit will be enabled if a file has three times more downloads than views. The
|
||||||
owner of a file can always download it. When a file is rate limited the user
|
owner of a file can always download it. When a file is rate limited the user
|
||||||
will need to fill out a captcha in order to continue downloading the file. The
|
will need to fill out a captcha in order to continue downloading the file. The
|
||||||
captcha will only appear on the file viewer page (pixeldrain.com/u/{id}). Rate
|
captcha will only appear on the file viewer page (nova.storage/u/{id}). Rate
|
||||||
limiting has been added to prevent the spread of viruses and to stop hotlinking.
|
limiting has been added to prevent the spread of viruses and to stop hotlinking.
|
||||||
Hotlinking is only allowed when files are uploaded using a Pro account.
|
Hotlinking is only allowed when files are uploaded using a Pro account.
|
||||||
|
|
||||||
Pixeldrain also includes a virus scanner. If a virus has been detected in a file
|
Nova also includes a virus scanner. If a virus has been detected in a file
|
||||||
the user will also have to fill in a captcha to download it.
|
the user will also have to fill in a captcha to download it.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ Warning: If a file is using too much bandwidth it can be rate limited. The rate
|
|||||||
limit will be enabled if a file has ten times more downloads than views. The
|
limit will be enabled if a file has ten times more downloads than views. The
|
||||||
owner of a file can always download it. When a file is rate limited the user
|
owner of a file can always download it. When a file is rate limited the user
|
||||||
will need to fill out a captcha in order to continue downloading the file. The
|
will need to fill out a captcha in order to continue downloading the file. The
|
||||||
captcha will only appear on the file viewer page (pixeldrain.com/u/{id}). Rate
|
captcha will only appear on the file viewer page (nova.storage/u/{id}). Rate
|
||||||
limiting has been added to prevent the spread of viruses and to stop direct
|
limiting has been added to prevent the spread of viruses and to stop direct
|
||||||
linking.
|
linking.
|
||||||
|
|
||||||
Pixeldrain also includes a virus scanner. If a virus has been detected in a file
|
Nova also includes a virus scanner. If a virus has been detected in a file
|
||||||
the user will also have to fill in a captcha to download it.
|
the user will also have to fill in a captcha to download it.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ POST body should be a JSON object, example below. A list can contain at most
|
|||||||
#### Example
|
#### Example
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"title": "My beautiful photos", // Defaults to "Pixeldrain List"
|
"title": "My beautiful photos", // Defaults to "Nova List"
|
||||||
"anonymous": false / true, // If true this list will not be linked to your user account. Defaults to "false"
|
"anonymous": false / true, // If true this list will not be linked to your user account. Defaults to "false"
|
||||||
"files": [ // Ordered array of files to add to the list
|
"files": [ // Ordered array of files to add to the list
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
# Pixeldrain business plan
|
|
||||||
|
|
||||||
## The problem
|
|
||||||
|
|
||||||
Sending large files to people is still hard. In the last 20 years we have made
|
|
||||||
little progress in this area.
|
|
||||||
|
|
||||||
## The competition
|
|
||||||
|
|
||||||
There is no shortage of file sharing sites on the internet. Some of them are
|
|
||||||
very large, but none of them are perfect.
|
|
||||||
|
|
||||||
### WeTransfer
|
|
||||||
|
|
||||||
WeTransfer is the world's 410'th largest website. It gets an estimated 140
|
|
||||||
million unique visitors per month. WeTransfer is loved by its users, but it has
|
|
||||||
many flaws in my opinion.
|
|
||||||
|
|
||||||
* ✅ Minimalistic user interface. Easy to understand
|
|
||||||
* ✅ Ads do not get in the way
|
|
||||||
* ❌ Slow and hard to navigate website
|
|
||||||
* ❌ Unclear what the limits are
|
|
||||||
* ❌ No way to view video, audio and images online
|
|
||||||
* ❌ Trackers: Google Tag Manager, no privacy for users
|
|
||||||
* ❌ Pro plan is expensive (€12 / m)
|
|
||||||
* ❌ Low limits. Files expire after only 1 week and max file size is only 2 GB
|
|
||||||
|
|
||||||
### Zippyshare
|
|
||||||
|
|
||||||
Zippyshare is the world's 850'th largest website. It gets around 56 million
|
|
||||||
unique visitors per month. Despite its size and popularity I think it should be
|
|
||||||
easy to overtake. Users are not very loyal and it mostly gets used for piracy.
|
|
||||||
|
|
||||||
* ✅ Easy to use
|
|
||||||
* ✅ Clearly defined limits
|
|
||||||
* ✅ File retention is 30 days after the last download, same as pixeldrain
|
|
||||||
* ✅ Very fast download speed
|
|
||||||
* ✅ No limits on how many times a file can be downloaded
|
|
||||||
* ✅ Viewing files online is possible
|
|
||||||
* ❌ Low size limit (500 MB per file)
|
|
||||||
* ❌ Outdated design (looks like a website from 15 years ago)
|
|
||||||
* ❌ Horrible ads on download page (push notifications, popunders, clickjackers,
|
|
||||||
fake download buttons). Not the kind of thing you'd want to send to your
|
|
||||||
family and friends
|
|
||||||
* ❌ No premium plan (at least not well advertised)
|
|
||||||
|
|
||||||
## The solution
|
|
||||||
|
|
||||||
Pixeldrain presents itself as the best of both worlds:
|
|
||||||
|
|
||||||
* ✅ A clear user interface which guides the user through the process of
|
|
||||||
uploading and sharing files
|
|
||||||
* ✅ Fast content delivery servers around the world make sure your files are
|
|
||||||
always right around the corner
|
|
||||||
* ✅ Clearly defined and liberating file size limit of 5 GB per file. The
|
|
||||||
highest in the industry
|
|
||||||
* ✅ Files stay around until 30 days after the last time it's downloaded
|
|
||||||
* ✅ Because pixeldrain does not use trackers we can't see what you're sharing,
|
|
||||||
and neither can Google or Facebook. This makes pixeldrain the ultimate
|
|
||||||
solution for privacy-minded people
|
|
||||||
|
|
||||||
## Monetization
|
|
||||||
|
|
||||||
### Pixeldrain Pro
|
|
||||||
|
|
||||||
A subscription plan of €12 per _year_ (that's 10x cheaper than WeTransfer!).
|
|
||||||
This amount of money should be enough to cover all the usage costs for most
|
|
||||||
users. With this you will get:
|
|
||||||
|
|
||||||
* File size limit will be raised from 5 GB to 10 GB.
|
|
||||||
|
|
||||||
* File expiration will be increased from 30 days to 60 days after the last
|
|
||||||
download.
|
|
||||||
|
|
||||||
* No ads on files you share, the download page will be completely clean. This
|
|
||||||
results in a faster and cleaner experience for people who receive the shared
|
|
||||||
files. Which should make them more interested in the product.
|
|
||||||
|
|
||||||
* No trackers on the site, everything your browser requests will come from
|
|
||||||
pixeldrain's servers. This caters to the privacy conscious audience. This
|
|
||||||
market has been booming lately, just take a look at all the VPN companies and
|
|
||||||
all the social media scandals.
|
|
||||||
|
|
||||||
This plan does not diable the rate limiting which kicks in when a file has 3
|
|
||||||
times more downloads than views. So direct linking is not an option. To solve
|
|
||||||
that we have to second option.
|
|
||||||
|
|
||||||
Pixeldrain doesn't actually need people to subscribe in order to survive. But I
|
|
||||||
think turning off ads and trackers is a basic right that every website should
|
|
||||||
have. This plan only really starts getting profitable when more than 10 000
|
|
||||||
people sign up for it. I will still have to look for advertising deals.
|
|
||||||
|
|
||||||
### Pixeldrain Enterprise
|
|
||||||
|
|
||||||
We will also cater to business owners and professional file sharers with a
|
|
||||||
pay-as-you-go subscription. This subscription will charge €6 per TB for storage
|
|
||||||
and €2 per TB for bandwidth respectively. This is extremely cheap for content
|
|
||||||
delivery standards. For this money you will get a product which does not really
|
|
||||||
offer what a real CDN would (automatic file caching and load reduction), but you
|
|
||||||
will get an easy and cheap method for sharing huge files to enormous amount of
|
|
||||||
people. And that at one tenth of the cost of a regular CDN.
|
|
||||||
|
|
||||||
Apart from that you will also get:
|
|
||||||
|
|
||||||
* Access to pixeldrain's Buckets feature. This allows you to create public
|
|
||||||
directories which you can use to share large datasets selectively. And easily
|
|
||||||
manage your files through a blazing fast web UI or even SFTP.
|
|
||||||
|
|
||||||
* Whitelabel download pages. Brand your download pages with custom colours,
|
|
||||||
icons and background graphics.
|
|
||||||
|
|
||||||
* You are completely shielded from all of pixeldrain's limits. Rate limiting
|
|
||||||
will be turned off and you will be able to directly link to your files
|
|
||||||
instead of having to go through a separate download page.
|
|
||||||
|
|
||||||
* A billshock prevention mechanism will prevent people from repeatedly
|
|
||||||
downloading your files and increasing your usage.
|
|
||||||
|
|
||||||
## Spreading the word
|
|
||||||
|
|
||||||
And this is the beautiful part. Pixeldrain's download page already sees millions
|
|
||||||
of users _per day_! I don't have to spend a ton of money on marketing and
|
|
||||||
advertising. All I have to do to let people know about all this is place some
|
|
||||||
witty ads on my own download page.
|
|
||||||
|
|
||||||
* Files expiring too soon? Pixeldrain Pro, only €1 per month!
|
|
||||||
|
|
||||||
* Tired of those annoying ads? Pixeldrain Pro, only €1 per month!
|
|
||||||
|
|
||||||
* Are your files too large for other sites too handle? Pixeldrain Pro, only €1
|
|
||||||
per month!
|
|
||||||
|
|
||||||
* Are Google and Facebook following you around the internet? Pixeldrain Pro,
|
|
||||||
only €1 per month!
|
|
||||||
|
|
||||||
* Et cetera
|
|
||||||
|
|
||||||
## TODO: How pixeldrain can afford to be cheaper
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
# Filesystem Guide
|
|
||||||
|
|
||||||
Pixeldrain has an experimental filesystem feature. It can be accessed from any
|
|
||||||
account with a paid subscription (Patreon or Prepaid) by going to
|
|
||||||
[pixeldrain.com/d/me](/d/me).
|
|
||||||
|
|
||||||
* **IMPORTANT**: The filesystem is still in development. This means that it's
|
|
||||||
not finished yet. While the filesystem seems stable now and I am using it
|
|
||||||
personally too, you are strongly advised to keep backups of anything you
|
|
||||||
upload here.
|
|
||||||
* If you experience any issues while using the filesystem, feel free to discuss
|
|
||||||
them on [the Discord community](https://discord.gg/TWKGvYAFvX).
|
|
||||||
|
|
||||||
Contents
|
|
||||||
|
|
||||||
[TOC]
|
|
||||||
|
|
||||||
## Pricing
|
|
||||||
|
|
||||||
Every time you create or remove a file your account's storage usage will be
|
|
||||||
updated. This can take some time. If your account's storage is full you will no
|
|
||||||
longer be able to upload anything to the filesystem.
|
|
||||||
|
|
||||||
The Pro subscription has a storage limit of 2 TB. It doesn't show on the profile
|
|
||||||
page because it's calculated differently from the other plans, but it is there.
|
|
||||||
|
|
||||||
For Prepaid plans the storage is charged at €4 per TB per month. You can view
|
|
||||||
your usage in the [transaction log](/user/prepaid/transactions).
|
|
||||||
|
|
||||||
Downloads from the filesystem are charged at €1 / TB for prepaid. With Patreon
|
|
||||||
plans there's a monthly limit. If you turn hotlinking off in the account
|
|
||||||
settings then other people will use their own daily download limit. Otherwise
|
|
||||||
they will use your account's transfer limit.
|
|
||||||
|
|
||||||
### Metadata storage cost
|
|
||||||
|
|
||||||
Files and directories stored in the filesystem also use space in the database.
|
|
||||||
To account for this we added an extra rule when counting space usage. All files
|
|
||||||
under 1000 bytes will be rounded up to 1000 bytes. All directories in the
|
|
||||||
filesystem will also use 1000 bytes of storage space. There are a couple of
|
|
||||||
reasons why this rule was put into place:
|
|
||||||
|
|
||||||
* Storing metadata like file names and permissions costs space in the database.
|
|
||||||
Database storage is much more expensive than bulk data storage.
|
|
||||||
* If directories were free to store, you could theoretically store unlimited
|
|
||||||
amounts of data in directory names and not pay for it.
|
|
||||||
* Accounts with lots of small files and directories slow down database
|
|
||||||
maintenance processes.
|
|
||||||
* Directories on other filesystems like ext4 also use storage space. Typically
|
|
||||||
that's 4 KiB, so 1000 bytes is still low in comparison.
|
|
||||||
|
|
||||||
To put the pricing into perspective: Storing 1000 bytes on pixeldrain costs
|
|
||||||
€0,000000004. For €1 a month you can store 250 million directories.
|
|
||||||
|
|
||||||
## Free download limit
|
|
||||||
|
|
||||||
The pixeldrain filesystem uses the same download limit as the regular files on
|
|
||||||
pixeldrain. The only difference is that the limit is 1 GB higher. So while you
|
|
||||||
can freely download up to 5 GB per day from regular pixeldrain files, you can
|
|
||||||
download up to 6 GB per day from the filesystem. When the limit is exceeded the
|
|
||||||
speed is limited to 1 MiB/s like usual.
|
|
||||||
|
|
||||||
If you want to embed pixeldrain files on your own website, distribute direct
|
|
||||||
download links or share files that are larger than the download limit then you
|
|
||||||
should turn the 'Hotlinking' option on. Otherwise people will have trouble
|
|
||||||
downloading your files.
|
|
||||||
|
|
||||||
## Directory sharing
|
|
||||||
|
|
||||||
Files in the the filesystem are private by default. Only you can access them
|
|
||||||
from your own account. Files and directories can be shared by clicking the
|
|
||||||
`Share` button in the toolbar while inside the directory, or by clicking the
|
|
||||||
pencil icon next to the directory in the file viewer.
|
|
||||||
|
|
||||||
Shared directories and files will have a shared icon next to them in the file
|
|
||||||
manager. Clicking that icon will open the shared link. You can also copy the
|
|
||||||
shared link directly with the `Copy link` button in the toolbar.
|
|
||||||
|
|
||||||
If a shared file gets reported for breaking the [content policy](/abuse) your
|
|
||||||
ability to share files from your account may be taken away.
|
|
||||||
|
|
||||||
## Limits
|
|
||||||
|
|
||||||
Here is a quick overview of the filesystem's limits:
|
|
||||||
|
|
||||||
* Max 10000 files per directory
|
|
||||||
* Max file size is 100 GB
|
|
||||||
* File/directory names can be up to 255 bytes long
|
|
||||||
* Path names can be up to 4095 bytes long
|
|
||||||
* You can have a maximum of 64 nested directories
|
|
||||||
* The filesystem does not support hard or symbolic links, this might change
|
|
||||||
later
|
|
||||||
|
|
||||||
When traversing a path, pixeldrain requests one directory at a time from the
|
|
||||||
database. This means that filesystem operations will get slower the more nested
|
|
||||||
directories you have. Keep that in mind when organizing your files.
|
|
||||||
|
|
||||||
## Importing files
|
|
||||||
|
|
||||||
It's possible to import files from your account's file list to your filesystem.
|
|
||||||
To do so, navigate to a directory in your filesystem, click the `Import files`
|
|
||||||
button on the toolbar. It's on the right side, between the `Create directory`
|
|
||||||
and `Edit files` buttons. You will be prompted to select the files you would
|
|
||||||
like to import. After selecting the files click `Add` and they will be added to
|
|
||||||
your filesystem.
|
|
||||||
|
|
||||||
## Client integrations
|
|
||||||
|
|
||||||
There are two ways to access your filesystem from outside the web interface.
|
|
||||||
|
|
||||||
### Rclone
|
|
||||||
|
|
||||||
Rclone has built in support for the pixeldrain filesystem starting with version
|
|
||||||
1.68. Check out [the rclone website](https://rclone.org/pixeldrain/) for
|
|
||||||
documentation. You can install rclone [from the
|
|
||||||
site](https://rclone.org/downloads/). It's also available in most software
|
|
||||||
repositories.
|
|
||||||
|
|
||||||
A few example use cases of rclone are:
|
|
||||||
|
|
||||||
* Mount pixeldrain as a network drive with [rclone
|
|
||||||
mount](https://rclone.org/commands/rclone_mount/) (instructions below)
|
|
||||||
* Create a backup of your local storage with [rclone
|
|
||||||
sync](https://rclone.org/commands/rclone_sync/)
|
|
||||||
* Perform a two-way sync with [rclone
|
|
||||||
bisync](https://rclone.org/commands/rclone_bisync/) (bisync is experimental
|
|
||||||
tech, don't use with important data)
|
|
||||||
|
|
||||||
#### Rclone upload performance issue
|
|
||||||
|
|
||||||
Under some circumstances rclone has trouble with reaching full upload speed when
|
|
||||||
using the HTTP2 protocol (which is enabled by default). This is due to a [bug in
|
|
||||||
the Go runtime](https://github.com/golang/go/issues/37373). If you are
|
|
||||||
experiencing this then it might help to add the `--disable-http2` flag to your
|
|
||||||
rclone commands.
|
|
||||||
|
|
||||||
#### Rclone mount systemd service example
|
|
||||||
|
|
||||||
To automatically mount your pixeldrain when logging in to your Linux OS you can
|
|
||||||
use a systemd user service.
|
|
||||||
|
|
||||||
First you must configure an rclone remote with the name `Pixeldrain`. This will
|
|
||||||
be the name of the network drive as well. You can choose a different name if you
|
|
||||||
want to.
|
|
||||||
|
|
||||||
1. Run `rclone config` to start the interactive configuration prompt.
|
|
||||||
2. Press `n` to create a new remote.
|
|
||||||
3. Enter the name `Pixeldrain`, or a different name if you want.
|
|
||||||
4. When asked which storage provider you want to use enter `pixeldrain`.
|
|
||||||
5. Follow the rest of the instructions.
|
|
||||||
|
|
||||||
Create a text file with these contents at the path
|
|
||||||
`$HOME/.config/systemd/user/rclone@.service`. You may have to create the parent
|
|
||||||
directories yourself.
|
|
||||||
|
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=rclone: Remote FUSE filesystem for cloud storage config %i
|
|
||||||
Documentation=man:rclone(1)
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
AssertPathIsDirectory=%h/%i
|
|
||||||
StartLimitBurst=5
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=rclone mount \
|
|
||||||
--config=%h/.config/rclone/rclone.conf \
|
|
||||||
--vfs-cache-mode full \
|
|
||||||
--vfs-cache-max-age 720h \
|
|
||||||
--vfs-cache-min-free-space 50G \
|
|
||||||
--vfs-write-back 10s \
|
|
||||||
--dir-cache-time 10m \
|
|
||||||
--log-level INFO \
|
|
||||||
--transfers 10 \
|
|
||||||
--file-perms 0700 \
|
|
||||||
--dir-perms 0700 \
|
|
||||||
%i: %h/%i
|
|
||||||
|
|
||||||
KillSignal=SIGINT
|
|
||||||
TimeoutStartSec=600
|
|
||||||
Restart=on-failure
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the file is in place, reload your systemd config with `systemctl --user
|
|
||||||
daemon-reload`. Then you can start your network drive with `systemctl --user
|
|
||||||
enable rclone@Pixeldrain.service --now`, where `Pixeldrain` is the name of your
|
|
||||||
rclone remote (replace with the name of your own remote if necessary). This will
|
|
||||||
create a directory called `Pixeldrain` in your home which will contain your
|
|
||||||
network drive. If it doesn't work, you can check the logs with `journalctl
|
|
||||||
--user -u rclone@Pixeldrain`.
|
|
||||||
|
|
||||||
If you can't get it to work you can always ask for help on our [Discord
|
|
||||||
community](https://discord.gg/TWKGvYAFvX).
|
|
||||||
|
|
||||||
### FTPS
|
|
||||||
|
|
||||||
The filesystem also supports FTPS, both anonymously and with an account. The FTP
|
|
||||||
server is hosted at `pixeldrain.com` on port `990`. The encryption mode used is
|
|
||||||
`Implicit FTP over TLS`. Here is an example configuration in FileZilla:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
When using FileZilla please make sure you have the "Data transfer mode" set to
|
|
||||||
Binary. If you do not do this FileZilla will corrupt your files if it does not
|
|
||||||
detect the file type correctly. This can happen if you have files without a file
|
|
||||||
extension. Data transfer mode can be found in the Settings screen under
|
|
||||||
Transfers > FTP: File Types. Set to "Binary" to prevent FileZilla from
|
|
||||||
corrupting your data.
|
|
||||||
|
|
||||||
There are two different ways to log in to the FTP server:
|
|
||||||
|
|
||||||
#### Read-write personal directory
|
|
||||||
|
|
||||||
To connect to your personal directory you need to enter your account's username
|
|
||||||
as username in the FTP client. The password needs to be an API key from the [API
|
|
||||||
keys page](/user/api_keys). If you connect now you will be able to access your
|
|
||||||
personal directory (called `/me`). Here you can upload and download to your
|
|
||||||
heart's desire.
|
|
||||||
|
|
||||||
#### Read-only shared directory
|
|
||||||
|
|
||||||
To access a shared directory in read-only mode you need to enter the directory
|
|
||||||
ID as username in your FTP client. The directory ID can be found at the end of a
|
|
||||||
shared directory URL. Example: `https://pixeldrain.com/d/abcd1234`, in this case
|
|
||||||
`abcd1234` is the directory ID. The ID will always be 8 characters long and is
|
|
||||||
case-sensitive. The password must be left empty
|
|
||||||
|
|
||||||
## Filesystem API
|
|
||||||
|
|
||||||
There is a public filesystem REST API, but it is not documented currently. The
|
|
||||||
best reference for how to use the API would be the TypeScript client which the
|
|
||||||
website uses as well. That can be found [on
|
|
||||||
GitHub](https://github.com/Fornaxian/pixeldrain_web/blob/master/svelte/src/filesystem/FilesystemAPI.mts).
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# Sia hosting guidelines
|
|
||||||
|
|
||||||
Pixeldrain uses [Sia](https://sia.tech) to offload files which are not requested
|
|
||||||
often, but still need to be kept. Sia is a free storage market where any host
|
|
||||||
can choose their own pricing. Because of this the users of the network need to
|
|
||||||
be careful when choosing the hosts to make contracts with.
|
|
||||||
|
|
||||||
Because pixeldrain is fairly cost-constrained we are forced to set some hard
|
|
||||||
requirements on storage and bandwidth pricing for Sia hosts.
|
|
||||||
|
|
||||||
## Rates
|
|
||||||
|
|
||||||
We will only make contracts with hosts that fullfill all these requirements.
|
|
||||||
Keep in mind that these are maximums, you are allowed to go lower.
|
|
||||||
|
|
||||||
{{$price := .PixelAPI.GetSiaPrice}}
|
|
||||||
|
|
||||||
| Requirement | Max rate EUR | Max rate SC |
|
|
||||||
|--------------------------|--------------|-------------|
|
|
||||||
| Storage price per month | € 1.50 / TB | {{ div 1.5 $price | formatSC }} / TB |
|
|
||||||
| Download price | € 2.00 / TB | {{ div 2.0 $price | formatSC }} / TB |
|
|
||||||
| Upload price | € 0.50 / TB | {{ div 0.5 $price | formatSC }} / TB |
|
|
||||||
| Contract formation price | € 0.10 | {{ div 0.1 $price | formatSC }} |
|
|
||||||
|
|
||||||
<sup>
|
|
||||||
Based on exchange rates from Kraken.
|
|
||||||
</sup>
|
|
||||||
|
|
||||||
This may seem low, but keep in mind that these prices are before redundancy. We
|
|
||||||
have to upload all our data three times to the Sia network in order to reach
|
|
||||||
high availability. If you multiply everything by three it becomes much more
|
|
||||||
reasonable.
|
|
||||||
|
|
||||||
We also can't guarantee that your host will be picked when it fulfills these
|
|
||||||
requirements. If there is enough supply we will only pick the most reliable
|
|
||||||
hosts available.
|
|
||||||
|
|
||||||
Other settings we pay attention to:
|
|
||||||
|
|
||||||
| Setting | Recommended value |
|
|
||||||
|-----------------------|-------------------|
|
|
||||||
| Max contract duration | At least 4 months |
|
|
||||||
| Proof window duration | 1 day |
|
|
||||||
| Download batch size | At least 16 MiB |
|
|
||||||
| Revision batch size | At least 16 MiB |
|
|
||||||
|
|
||||||
## Tips and tricks for becoming a better host
|
|
||||||
|
|
||||||
### Use a stable Linux or BSD-based operating system
|
|
||||||
|
|
||||||
Sia is known to run better on Linux or BSD based operating systems. Windows is
|
|
||||||
discouraged due to I/O reliability issues. Windows often sacrifices reliability
|
|
||||||
for better performance, because of this crashes are more common on Windows and
|
|
||||||
also have a greater chance of resulting is data loss. Forced updates and other
|
|
||||||
interruping system processes are also likely to harm hosting uptime and
|
|
||||||
performance.
|
|
||||||
|
|
||||||
We can recommend Debian, CentOS or Ubuntu LTS for hosting. These are systems
|
|
||||||
which are known to be able to run uninterruped for decades. They are also
|
|
||||||
regularly patched with security updates which don't even require restarting most
|
|
||||||
of the time. This makes these systems perfect for the role of hosting on Sia.
|
|
||||||
|
|
||||||
### Enable TCP BBR and other network stack optimizations
|
|
||||||
|
|
||||||
BBR is a new congestion control agorithm which dramatically decreases the time
|
|
||||||
needed for a TCP connection to ramp up to maximum speed. It also contains
|
|
||||||
improvements to counter other problems like router buffer bloat which causes
|
|
||||||
network latency spikes. Here's an [in-depth analysis of the benefits of enabling
|
|
||||||
BBR](https://blog.apnic.net/2017/05/09/bbr-new-kid-tcp-block/).
|
|
||||||
|
|
||||||
To enable BBR you need you have kernel version 4.9 or higher. See your kernel
|
|
||||||
version with `uname -a`. On Ubuntu you can upgrade to a newer kernel by
|
|
||||||
[enabling HWE](https://wiki.ubuntu.com/Kernel/LTSEnablementStack).
|
|
||||||
|
|
||||||
Create a file called `/etc/sysctl.d/60-bbr.conf` with the following contents:
|
|
||||||
|
|
||||||
```
|
|
||||||
net.core.default_qdisc = fq_codel
|
|
||||||
net.ipv4.tcp_congestion_control = bbr
|
|
||||||
net.ipv4.tcp_notsent_lowat = 16384
|
|
||||||
net.ipv4.tcp_slow_start_after_idle = 0
|
|
||||||
```
|
|
||||||
|
|
||||||
After doing that you can run `sysctl -p` or reboot to apply the changes. Verify
|
|
||||||
that it's working with this command: `sysctl net.ipv4.tcp_congestion_control`.
|
|
||||||
It should return `bbr`.
|
|
||||||
|
|
||||||
Here's a more in-depth [guide to the configuration of the linux network
|
|
||||||
stack](https://www.cyberciti.biz/cloud-computing/increase-your-linux-server-internet-speed-with-tcp-bbr-congestion-control/).
|
|
||||||
|
|
||||||
### Use Sia Host Manager to configure your host
|
|
||||||
|
|
||||||
SiaCentral's [Host Manager](https://siacentral.com/host-manager) is a great tool
|
|
||||||
for monitoring and configuring your host. It explains all the settings in
|
|
||||||
detail, gives an option to set prices in any currency you like and gives
|
|
||||||
detailed insights into your contracts and revenue stream.
|
|
||||||
|
|
||||||
### Sign up for SiaStats host alerts
|
|
||||||
|
|
||||||
When your host is configured properly SiaStats will monitor its uptime and
|
|
||||||
performance. These stats are important for renters to discover good hosts and to
|
|
||||||
get an overview into the state of the hosting network.
|
|
||||||
|
|
||||||
If your host has been online for a while it will show up on SiaStats' [hosting
|
|
||||||
page](https://siastats.info/hosts). If you search for your host there will be an
|
|
||||||
option to sign up for hosting alerts.
|
|
||||||
|
|
||||||
### IPv6 capability is encouraged
|
|
||||||
|
|
||||||
Pixeldrain makes heavy use of IPv6 across its systems. We do this because we
|
|
||||||
believe that IPv6 is a critical component for the free internet. The old IPv4
|
|
||||||
requires terrible hacks like NAT to work at a large scale. IPv4 addresses are
|
|
||||||
also scarce and expensive to rent. All this money is thrown away on a legacy
|
|
||||||
system for which a replacement has already existed for over a decade. NAT limits
|
|
||||||
the growth of peer-to-peer software by making it impossible for applications to
|
|
||||||
communicate freely over the internet. Instead we need to add more hacks on top
|
|
||||||
like port forwarding to make it work. This has harmed the growth of the open
|
|
||||||
internet a lot over the decades and will harm it more if we keep going like
|
|
||||||
this.
|
|
||||||
|
|
||||||
So enable IPv6. If you don't have IPv6, call your ISP and ask them why not.
|
|
||||||
|
|
||||||
<div style="margin-top: 100px; height: 128px; text-align: center;">
|
|
||||||
<a href="https://sia.tech/">
|
|
||||||
<img style="height: 100%;" src="/res/img/misc/built-with-Sia-mono.svg" alt="Built with Sia"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Limits
|
|
||||||
|
|
||||||
We need to limit the features of pixeldrain to avoid abuse and encourage you to
|
|
||||||
get a subscription. Here's an overview of all limits which are currently in
|
|
||||||
place on pixeldrain.
|
|
||||||
|
|
||||||
| | Anonymous | Registered | Pro | Prepaid |
|
|
||||||
|-----------------------|--------------|-----------------|---------------|---------------------|
|
|
||||||
| Download speed | 2 MiB/s | 2 MiB/s | Unlimited | Unlimited |
|
|
||||||
| File storage | Unlimited | 500 GB | 1 TB | Unlimited (€4 / TB) |
|
|
||||||
| File expiry | 30 days | 30 days | 90 days | Unlimited |
|
|
||||||
| File size | 10 GB | 10 GB | 20 GB | 20 GB |
|
|
||||||
| File downloads | 1000 / day | Unlimited | Unlimited | Unlimited |
|
|
||||||
| Free data transfer | 100 GB / day | Unlimited | Unlimited | Unlimited |
|
|
||||||
| Premium data transfer | No | 10 GB / month | 1 TB / month | Unlimited (€1 / TB) |
|
|
||||||
| Video streaming | No | Within data cap | Yes | Yes |
|
|
||||||
| Ad-free file viewing | No | No | Yes | Yes |
|
|
||||||
| File viewer branding | No | No | From €8/month | Yes |
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -7,7 +7,7 @@
|
|||||||
<body>
|
<body>
|
||||||
{{template "page_top" .}}
|
{{template "page_top" .}}
|
||||||
<header>
|
<header>
|
||||||
<h1>You broke pixeldrain</h1>
|
<h1>You broke Nova</h1>
|
||||||
</header>
|
</header>
|
||||||
<div id="page_content" class="page_content">
|
<div id="page_content" class="page_content">
|
||||||
<section>
|
<section>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
Great job.
|
Great job.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Just kidding, something went wrong on the pixeldrain server and
|
Just kidding, something went wrong on the Nova server and
|
||||||
it's my fault. This can happen when the website is overloaded,
|
it's my fault. This can happen when the website is overloaded,
|
||||||
there is a problem with the software or there is a hardware
|
there is a problem with the software or there is a hardware
|
||||||
problem. When there is a large scale outage you can usually find
|
problem. When there is a large scale outage you can usually find
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
{{ define "apps" }}<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
{{template "meta_tags" "Apps"}}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.specs {
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 1px solid var(--separator);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
{{template "page_top" .}}
|
|
||||||
<header>
|
|
||||||
<h1>Apps</h1>
|
|
||||||
</header>
|
|
||||||
<div id="page_content" class="page_content">
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2>ShareX</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Windows | License: GPL-3.0 |
|
|
||||||
<a href="https://getsharex.com">Website</a> |
|
|
||||||
<a href="https://github.com/ShareX/ShareX">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<img src="/res/img/sharex.png" style="float: right; height: 6em; margin: 0.5em;" />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
ShareX is a Screen capture, file sharing and productivity tool.
|
|
||||||
Pixeldrain is supported as a custom uploader. You can <a
|
|
||||||
href="https://getsharex.com/">get ShareX
|
|
||||||
here</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You can download a custom ShareX uploader profile which uses
|
|
||||||
your pixeldrain account for uploading files on the <a
|
|
||||||
href="/user/connect_app?app=sharex">account apps page</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Setting pixeldrain as default uploader</h3>
|
|
||||||
<p>
|
|
||||||
Download the uploader config and choose 'Open file'<br/>
|
|
||||||
<img src="/res/img/sharex_download.png" style="max-width: 100%;" /><br/>
|
|
||||||
Set pixeldrain.com as active uploader. Choose Yes<br/>
|
|
||||||
<img src="/res/img/sharex_default.png" style="max-width: 100%;" /><br/>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<br/>
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2>Drainy</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Windows, Linux, Android |
|
|
||||||
License: MIT |
|
|
||||||
<a href="https://github.com/ManuelReschke/go-pd-gui">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<img src="/res/img/drainy.png" style="float: right; height:
|
|
||||||
6em; margin: 0.5em;" /> A simple tool for uploading files to
|
|
||||||
pixeldrain. Supports uploading to accounts, copying download
|
|
||||||
links to the clipboard and has an upload history screen.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<br/>
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2 style="clear: both;">Pixeldrain Android</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Android |
|
|
||||||
License: None |
|
|
||||||
<a href="https://github.com/wimvdputten/Pixeldrain_android">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
An Android app for uploading and sharing files on
|
|
||||||
pixeldrain. You can get a compiled APK package from the <a
|
|
||||||
href="https://github.com/wimvdputten/Pixeldrain_android/releases">
|
|
||||||
GitHub releases page</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<br/>
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2>go-pd</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Linux, Mac OS, Windows (CLI) |
|
|
||||||
License: MIT |
|
|
||||||
<a href="https://github.com/ManuelReschke/go-pd">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
go-pd is a command line interface and client library for
|
|
||||||
pixeldrain created by Manuel Reschke. The CLI supports uploading
|
|
||||||
files anonymously, and uploading to user accounts. The client
|
|
||||||
library supports uploading, downloading and deleting files,
|
|
||||||
creating and viewing lists and user accounts. Compiled binaries
|
|
||||||
are available on the <a
|
|
||||||
href="https://github.com/ManuelReschke/go-pd/releases">
|
|
||||||
GitHub releases page</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<br/>
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2>go-pixeldrain</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Linux, Mac OS, Windows (CLI) |
|
|
||||||
License: MIT |
|
|
||||||
<a href="https://jkawamoto.github.io/go-pixeldrain/">Website</a> |
|
|
||||||
<a href="https://github.com/jkawamoto/go-pixeldrain">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
go-pixeldrain is a command line interface for pixeldrain
|
|
||||||
created by Junpei Kawamoto. You can use it to upload and
|
|
||||||
download files and directories from your terminal. Compiled
|
|
||||||
binaries are available on the <a
|
|
||||||
href="https://github.com/jkawamoto/go-pixeldrain/releases">
|
|
||||||
GitHub releases page</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<br/>
|
|
||||||
<section class="highlight_border" style="text-align: initial">
|
|
||||||
<h2>pdup</h2>
|
|
||||||
<div class="specs">
|
|
||||||
Platform: Linux, BSD, Mac OS (CLI) |
|
|
||||||
License: None |
|
|
||||||
<a href="https://github.com/Fornax96/pdup">GitHub</a>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
pdup is a little bash script for uploading files to pixeldrain
|
|
||||||
from the terminal. It's available <a
|
|
||||||
href="https://github.com/Fornax96/pdup">from
|
|
||||||
GitHub</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>More apps</h2>
|
|
||||||
<p>
|
|
||||||
If you know more open source apps which work with pixeldrain
|
|
||||||
please send them to
|
|
||||||
<a href="mailto:support@pixeldrain.com">support@pixeldrain.com</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{{template "page_bottom" .}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{ end }}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "meta_tags"}}
|
{{define "meta_tags"}}
|
||||||
<title>{{.}} ~ pixeldrain</title>
|
<title>{{.}} ~ Nova</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
||||||
<meta name="theme-color" content="#220735" />
|
<meta name="theme-color" content="#220735" />
|
||||||
@@ -7,20 +7,20 @@
|
|||||||
<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="128x128" href="/res/img/nova_128.png" />
|
||||||
<link rel="icon" sizes="152x152" href="/res/img/pixeldrain_152.png" />
|
<link rel="icon" sizes="152x152" href="/res/img/nova_152.png" />
|
||||||
<link rel="icon" sizes="180x180" href="/res/img/pixeldrain_180.png" />
|
<link rel="icon" sizes="180x180" href="/res/img/nova_180.png" />
|
||||||
<link rel="icon" sizes="192x192" href="/res/img/pixeldrain_192.png" />
|
<link rel="icon" sizes="192x192" href="/res/img/nova_192.png" />
|
||||||
<link rel="icon" sizes="196x196" href="/res/img/pixeldrain_196.png" />
|
<link rel="icon" sizes="196x196" href="/res/img/nova_196.png" />
|
||||||
<link rel="icon" sizes="256x256" href="/res/img/pixeldrain_256.png" />
|
<link rel="icon" sizes="256x256" href="/res/img/nova_256.png" />
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="/res/img/pixeldrain_152.png" />
|
<link rel="apple-touch-icon" sizes="152x152" href="/res/img/nova_152.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/res/img/pixeldrain_180.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/res/img/nova_180.png" />
|
||||||
<link rel="shortcut icon" sizes="196x196" href="/res/img/pixeldrain_196.png" />
|
<link rel="shortcut icon" sizes="196x196" href="/res/img/nova_196.png" />
|
||||||
|
|
||||||
<meta name="description" content="Pixeldrain is a file transfer service, you
|
<meta name="description" content="Nova is a file transfer service, you
|
||||||
can upload any file and you will be given a shareable link right away.
|
can upload any file and you will be given a shareable link right away.
|
||||||
pixeldrain also supports previews for images, videos, audio, PDFs and much more." />
|
Nova also supports previews for images, videos, audio, PDFs and much more." />
|
||||||
<meta name="keywords" content="file sharing, free file sharing, file transfer,
|
<meta name="keywords" content="file sharing, free file sharing, file transfer,
|
||||||
free file transfer, file hosting, free file hosting, hosting, file upload,
|
free file transfer, file hosting, free file hosting, hosting, file upload,
|
||||||
free file upload, uploading, send file, large file, free file sharing,
|
free file upload, uploading, send file, large file, free file sharing,
|
||||||
@@ -34,10 +34,10 @@ professional file transfer, cheap storage, cheap file storage, send large file,
|
|||||||
send big file, send huge file, audio sharing, music sharing, audio upload,
|
send big file, send huge file, audio sharing, music sharing, audio upload,
|
||||||
music upload" />
|
music upload" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="{{.}} ~ pixeldrain" />
|
<meta property="og:title" content="{{.}} ~ Nova" />
|
||||||
<meta property="og:site_name" content="pixeldrain" />
|
<meta property="og:site_name" content="Nova" />
|
||||||
<meta property="og:description" content="Instant file and screenshot sharing." />
|
<meta property="og:description" content="Instant file and screenshot sharing." />
|
||||||
<meta property="og:url" content="http://pixeldrain.com/" />
|
<meta property="og:url" content="http://nova.storage/" />
|
||||||
<meta property="og:image" content="/res/img/pixeldrain_256.png" />
|
<meta property="og:image" content="/res/img/nova_256.png" />
|
||||||
<meta property="og:image:type" content="image/png" />
|
<meta property="og:image:type" content="image/png" />
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -57,12 +57,10 @@ function resetMenu() {
|
|||||||
<footer>
|
<footer>
|
||||||
<div class="footer_content">
|
<div class="footer_content">
|
||||||
<div style="display: inline-block; margin: 0 8px;">
|
<div style="display: inline-block; margin: 0 8px;">
|
||||||
Pixeldrain is a product by <a href="//fornaxian.tech" target="_blank">Fornaxian Technologies</a>
|
Nova is a product by <a href="//fornaxian.tech" target="_blank">Fornaxian Technologies</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div style="display: inline-block; margin: 0 8px;">
|
<div style="display: inline-block; margin: 0 8px;">
|
||||||
<a href="https://www.patreon.com/pixeldrain" target="_blank">{{template `patreon.svg` .}} Patreon</a> |
|
|
||||||
<a href="https://reddit.com/r/pixeldrain" target="_blank">{{template `reddit.svg` .}} Reddit</a> |
|
|
||||||
<a href="https://github.com/Fornaxian" target="_blank">{{template `github.svg` .}} GitHub</a> |
|
<a href="https://github.com/Fornaxian" target="_blank">{{template `github.svg` .}} GitHub</a> |
|
||||||
<a href="https://mastodon.social/web/@fornax" target="_blank">{{template `mastodon.svg` .}} Mastodon</a>
|
<a href="https://mastodon.social/web/@fornax" target="_blank">{{template `mastodon.svg` .}} Mastodon</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<body>
|
<body>
|
||||||
{{template "page_top" .}}
|
{{template "page_top" .}}
|
||||||
<header>
|
<header>
|
||||||
<h1>Logging out of your pixeldrain account</h1>
|
<h1>Logging out of your Nova account</h1>
|
||||||
</header>
|
</header>
|
||||||
<div id="page_content" class="page_content">
|
<div id="page_content" class="page_content">
|
||||||
<br/>
|
<br/>
|
||||||
<form method="POST" action="/logout">
|
<form method="POST" action="/logout">
|
||||||
<button role="submit" class="button_highlight">
|
<button role="submit" class="button_highlight">
|
||||||
<i class="icon">logout</i>
|
<i class="icon">logout</i>
|
||||||
Log out of pixeldrain on this computer
|
Log out of Nova on this computer
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<br/>
|
<br/>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
We need you to confirm your action so we can be sure that you
|
We need you to confirm your action so we can be sure that you
|
||||||
really requested a logout. If we didn't do this, anyone (or any
|
really requested a logout. If we didn't do this, anyone (or any
|
||||||
website) would be able to send you to this page and you would
|
website) would be able to send you to this page and you would
|
||||||
automatically get logged out of pixeldrain, which would be very
|
automatically get logged out of Nova, which would be very
|
||||||
annoying.
|
annoying.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<iframe src="https://pixeldrain.com/u/Nygt1on4?embed" style="border: none; width: 800px; max-width: 100%; height: 600px; max-height: 100%; border-radius: 16px;"></iframe>
|
<iframe src="https://nova.storage/u/Nygt1on4?embed" style="border: none; width: 800px; max-width: 100%; height: 600px; max-height: 100%; border-radius: 16px;"></iframe>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{{template "page_bottom" .}}
|
{{template "page_bottom" .}}
|
||||||
|
|||||||
4
svelte/package-lock.json
generated
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "pixeldrain-web",
|
"name": "nova-web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pixeldrain-web",
|
"name": "nova-web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "pixeldrain-web",
|
"name": "nova-web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ let block_form = {
|
|||||||
let message = "The following files were blocked:<br/>"
|
let message = "The following files were blocked:<br/>"
|
||||||
message += "<ul>"
|
message += "<ul>"
|
||||||
jresp.files_blocked.forEach(file => {
|
jresp.files_blocked.forEach(file => {
|
||||||
message += "<li>pixeldrain.com/u/" + file + "</li>"
|
message += "<li>nova.storage/u/" + file + "</li>"
|
||||||
})
|
})
|
||||||
jresp.filesystem_nodes_blocked.forEach(file => {
|
jresp.filesystem_nodes_blocked.forEach(file => {
|
||||||
message += "<li>pixeldrain.com/d/" + file + "</li>"
|
message += "<li>nova.storage/d/" + file + "</li>"
|
||||||
})
|
})
|
||||||
message += "</ul>"
|
message += "</ul>"
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ onMount(() => {
|
|||||||
<section>
|
<section>
|
||||||
<h2>File removal</h2>
|
<h2>File removal</h2>
|
||||||
<p>
|
<p>
|
||||||
Paste any pixeldrain file links in here to remove them
|
Paste any Nova file links in here to remove them
|
||||||
</p>
|
</p>
|
||||||
<div class="highlight_border">
|
<div class="highlight_border">
|
||||||
<Form config={block_form}></Form>
|
<Form config={block_form}></Form>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import EmailReportersTable from "./EmailReportersTable.svelte";
|
import EmailReportersTable from "./EmailReportersTable.svelte";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
|
|
||||||
type Reporter = {
|
type Reporter = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Chart from "util/Chart.svelte";
|
|||||||
import { color_by_name } from "util/Util";
|
import { color_by_name } from "util/Util";
|
||||||
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
import ServerDiagnostics from "./ServerDiagnostics.svelte";
|
||||||
import PeerTable from "./PeerTable.svelte";
|
import PeerTable from "./PeerTable.svelte";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
let graphEgress: Chart = $state()
|
let graphEgress: Chart = $state()
|
||||||
let graphDownloads: Chart = $state()
|
let graphDownloads: Chart = $state()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import { check_response, get_endpoint } from "lib/PixeldrainAPI";
|
import { check_response, get_endpoint } from "lib/NovaAPI";
|
||||||
import hsl2rgb from "pure-color/convert/hsl2rgb";
|
import hsl2rgb from "pure-color/convert/hsl2rgb";
|
||||||
import rgb2hex from "pure-color/convert/rgb2hex";
|
import rgb2hex from "pure-color/convert/rgb2hex";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { formatDuration } from "util/Formatting";
|
import { formatDuration } from "util/Formatting";
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { onMount } from "svelte";
|
|||||||
import { formatDataVolume, formatDate } from "util/Formatting";
|
import { formatDataVolume, formatDate } from "util/Formatting";
|
||||||
import SortButton from "layout/SortButton.svelte";
|
import SortButton from "layout/SortButton.svelte";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { formatDate } from "util/Formatting";
|
import { formatDate } from "util/Formatting";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PixeldrainLogo from "util/PixeldrainLogo.svelte";
|
import NovaLogo from "util/NovaLogo.svelte";
|
||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import Euro from "util/Euro.svelte";
|
import Euro from "util/Euro.svelte";
|
||||||
import { formatDataVolume } from "util/Formatting";
|
import { formatDataVolume } from "util/Formatting";
|
||||||
@@ -10,7 +10,7 @@ let button: HTMLButtonElement = $state()
|
|||||||
let dialog: Dialog = $state()
|
let dialog: Dialog = $state()
|
||||||
|
|
||||||
let {
|
let {
|
||||||
no_login_label = "Pixeldrain",
|
no_login_label = "Nova",
|
||||||
hide_name = true,
|
hide_name = true,
|
||||||
hide_logo = false,
|
hide_logo = false,
|
||||||
style = "",
|
style = "",
|
||||||
@@ -32,7 +32,7 @@ const open = () => dialog.open(button.getBoundingClientRect())
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<button bind:this={button} onclick={open} class="button round" title="Menu" style={style}>
|
<button bind:this={button} onclick={open} class="button round" title="Menu" style={style}>
|
||||||
{#if !hide_logo}
|
{#if !hide_logo}
|
||||||
<PixeldrainLogo style="height: 1.6em; width: 1.6em;"/>
|
<NovaLogo style="height: 1.6em; width: 1.6em;"/>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="button_username" class:hide_name>
|
<span class="button_username" class:hide_name>
|
||||||
{$user.username === "" ? no_login_label : $user.username}
|
{$user.username === "" ? no_login_label : $user.username}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import { fs_delete_all, type FSNode } from "lib/FilesystemAPI.svelte";
|
import { fs_trash, type FSNode } from "lib/FilesystemAPI.svelte";
|
||||||
import type { FSNavigator } from "filesystem/FSNavigator";
|
import type { FSNavigator } from "filesystem/FSNavigator";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ const delete_file = async (e: MouseEvent) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
loading_start()
|
loading_start()
|
||||||
await fs_delete_all(file.path)
|
await fs_trash(file.path)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
alert(err)
|
alert(err)
|
||||||
@@ -61,10 +61,10 @@ const delete_file = async (e: MouseEvent) => {
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Delete</legend>
|
<legend>Delete</legend>
|
||||||
<p>
|
<p>
|
||||||
Delete this file or directory. If this is a directory then all
|
Move this file or directory to the trash. You can restore it or
|
||||||
subfiles will be deleted as well. This action cannot be undone.
|
permanently delete it from the <a href="/d/me/.Trash">trash bin</a>.
|
||||||
</p>
|
</p>
|
||||||
<Button click={delete_file} red icon="delete" label="Delete" style="align-self: flex-start;"/>
|
<Button click={delete_file} red icon="delete" label="Move to trash" style="align-self: flex-start;"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ run(() => {
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Embedding</legend>
|
<legend>Embedding</legend>
|
||||||
<p>
|
<p>
|
||||||
If you have a website you can embed pixeldrain directories and files in your
|
If you have a website you can embed Nova directories and files in your
|
||||||
own webpages with the code below. If you embed a directory then all
|
own webpages with the code below. If you embed a directory then all
|
||||||
subdirectories and files will be accessible through the frame. Branding
|
subdirectories and files will be accessible through the frame. Branding
|
||||||
options will also apply in the frame, but only when applied to this
|
options will also apply in the frame, but only when applied to this
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fs_delete_all, fs_rename } from "lib/FilesystemAPI.svelte"
|
import { fs_rename, fs_trash, FSNode } from "lib/FilesystemAPI.svelte"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import CreateDirectory from "./CreateDirectory.svelte"
|
import CreateDirectory from "./CreateDirectory.svelte"
|
||||||
import ListView from "./ListView.svelte"
|
import ListView from "./ListView.svelte"
|
||||||
@@ -15,6 +15,7 @@ import { FileAction, type FileActionHandler } from "./FileManagerLib";
|
|||||||
import FileMenu from "./FileMenu.svelte";
|
import FileMenu from "./FileMenu.svelte";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import SearchBar from "../SearchBar.svelte";
|
import SearchBar from "../SearchBar.svelte";
|
||||||
|
import type { GenericResponse } from "lib/NovaAPI";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
nav = $bindable(),
|
nav = $bindable(),
|
||||||
@@ -33,11 +34,11 @@ let {
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let large_icons = $state(false)
|
let large_icons = $state(false)
|
||||||
let uploader: FsUploadWidget
|
let uploader!: FsUploadWidget
|
||||||
let mode = $state("viewing")
|
let mode = $state("viewing")
|
||||||
let creating_dir = $state(false)
|
let creating_dir = $state(false)
|
||||||
let show_hidden = $state(false)
|
let show_hidden = $state(false)
|
||||||
let file_menu: FileMenu = $state()
|
let file_menu!: FileMenu
|
||||||
|
|
||||||
export const upload = (files: File[]) => {
|
export const upload = (files: File[]) => {
|
||||||
return uploader.upload_files(files)
|
return uploader.upload_files(files)
|
||||||
@@ -119,12 +120,12 @@ const delete_selected = async () => {
|
|||||||
loading_start()
|
loading_start()
|
||||||
|
|
||||||
// Save all promises with deletion requests in an array
|
// Save all promises with deletion requests in an array
|
||||||
let promises = []
|
let promises: Promise<GenericResponse>[] = []
|
||||||
nav.children.forEach(child => {
|
nav.children.forEach(child => {
|
||||||
if (!child.fm_selected) {
|
if (!child.fm_selected) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
promises.push(fs_delete_all(child.path))
|
promises.push(fs_trash(child.path))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait for all the promises to finish
|
// Wait for all the promises to finish
|
||||||
@@ -175,7 +176,7 @@ const toggle_large_icons = () => {
|
|||||||
|
|
||||||
// Moving functions
|
// Moving functions
|
||||||
|
|
||||||
let moving_items = []
|
let moving_items: FSNode[] = []
|
||||||
|
|
||||||
// We need to detect if shift is pressed so we can select multiple items
|
// We need to detect if shift is pressed so we can select multiple items
|
||||||
let shift_pressed = false
|
let shift_pressed = false
|
||||||
@@ -320,6 +321,14 @@ onMount(() => {
|
|||||||
<button onclick={() => nav.reload()} title="Refresh directory listing">
|
<button onclick={() => nav.reload()} title="Refresh directory listing">
|
||||||
<i class="icon">refresh</i>
|
<i class="icon">refresh</i>
|
||||||
</button>
|
</button>
|
||||||
|
{#if $nav.children.some(c => c.name === ".Trash" && c.type === "dir")}
|
||||||
|
<button
|
||||||
|
onclick={() => nav.navigate($nav.children.find(c => c.name === ".Trash").path, true)}
|
||||||
|
title="Open trash bin"
|
||||||
|
>
|
||||||
|
<i class="icon">delete</i>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toolbar_middle">
|
<div class="toolbar_middle">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { FSNavigator } from "filesystem/FSNavigator";
|
|||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import Dialog from "layout/Dialog.svelte";
|
import Dialog from "layout/Dialog.svelte";
|
||||||
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";
|
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";
|
||||||
import { fs_delete, type FSNode } from "lib/FilesystemAPI.svelte";
|
import { fs_trash, type FSNode } from "lib/FilesystemAPI.svelte";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import { tick } from "svelte";
|
import { tick } from "svelte";
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export const open = async (n: FSNode, target: EventTarget, event: Event) => {
|
|||||||
const delete_node = async () => {
|
const delete_node = async () => {
|
||||||
try {
|
try {
|
||||||
loading_start()
|
loading_start()
|
||||||
await fs_delete(node.path)
|
await fs_trash(node.path)
|
||||||
nav.reload()
|
nav.reload()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(JSON.stringify(err))
|
alert(JSON.stringify(err))
|
||||||
|
|||||||
@@ -26,8 +26,16 @@ let {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="name">Name</SortButton></td>
|
<td>
|
||||||
<td class="hide_small"><SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">Size</SortButton></td>
|
<SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="name">
|
||||||
|
Name
|
||||||
|
</SortButton>
|
||||||
|
</td>
|
||||||
|
<td class="hide_small">
|
||||||
|
<SortButton active_field={$nav.sort_last_field} asc={$nav.sort_asc} sort_func={nav.sort_children} field="file_size">
|
||||||
|
Size
|
||||||
|
</SortButton>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Uploads a file to the logged in user's pixeldrain account. If no user is
|
// Uploads a file to the logged in user's Nova account. If no user is
|
||||||
// logged in the file is uploaded anonymously.
|
// logged in the file is uploaded anonymously.
|
||||||
//
|
//
|
||||||
// on_progress reports progress on the file upload, parameter 1 is the uploaded
|
// on_progress reports progress on the file upload, parameter 1 is the uploaded
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const update = async () => {
|
|||||||
if (media_session) {
|
if (media_session) {
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: nav.base.name,
|
title: nav.base.name,
|
||||||
artist: "pixeldrain",
|
artist: "Nova",
|
||||||
album: "unknown",
|
album: "unknown",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ let magnet = $state("")
|
|||||||
instructions for your torrent client to download the files from
|
instructions for your torrent client to download the files from
|
||||||
other people who happen to be downloading the same files currently.
|
other people who happen to be downloading the same files currently.
|
||||||
This means that instead of connecting to a single server (like
|
This means that instead of connecting to a single server (like
|
||||||
pixeldrain), you will be connecting to other people on the internet
|
Nova), you will be connecting to other people on the internet
|
||||||
to download these files.
|
to download these files.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const update = async () => {
|
|||||||
if (media_session) {
|
if (media_session) {
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: node.name,
|
title: node.name,
|
||||||
artist: "pixeldrain",
|
artist: "Nova",
|
||||||
album: "unknown",
|
album: "unknown",
|
||||||
});
|
});
|
||||||
console.debug("Updating media session")
|
console.debug("Updating media session")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ onMount(async () => {
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
<p>
|
<p>
|
||||||
Your user account has been banned from uploading to
|
Your user account has been banned from uploading to
|
||||||
pixeldrain due to violation of the
|
Nova due to violation of the
|
||||||
<a href="/abuse">content policy</a>. Below is a list of
|
<a href="/abuse">content policy</a>. Below is a list of
|
||||||
files originating from your account which have been blocked:
|
files originating from your account which have been blocked:
|
||||||
</p>
|
</p>
|
||||||
@@ -63,7 +63,7 @@ onMount(async () => {
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
If you would like to dispute your account ban you can mail me at
|
If you would like to dispute your account ban you can mail me at
|
||||||
support@pixeldrain.com. Please do not mail unless you have a
|
support@nova.storage. Please do not mail unless you have a
|
||||||
good reason. If you do not provide a valid reason why the ban
|
good reason. If you do not provide a valid reason why the ban
|
||||||
should be reversed your e-mail will be ignored. And do not
|
should be reversed your e-mail will be ignored. And do not
|
||||||
forget to put your username ({window.user.username}) in the
|
forget to put your username ({window.user.username}) in the
|
||||||
@@ -87,7 +87,7 @@ onMount(async () => {
|
|||||||
{#if result.ip_banned}
|
{#if result.ip_banned}
|
||||||
<p>
|
<p>
|
||||||
Your IP address ({result.address}) has been banned from
|
Your IP address ({result.address}) has been banned from
|
||||||
uploading to pixeldrain due to violation of the
|
uploading to Nova due to violation of the
|
||||||
<a href="/abuse">content policy</a>. Below is a list of
|
<a href="/abuse">content policy</a>. Below is a list of
|
||||||
files originating from your IP address which have been
|
files originating from your IP address which have been
|
||||||
blocked:
|
blocked:
|
||||||
@@ -96,7 +96,7 @@ onMount(async () => {
|
|||||||
<p>
|
<p>
|
||||||
Your IP address ({result.address}) has received copyright
|
Your IP address ({result.address}) has received copyright
|
||||||
strikes. At 10 copyright strikes your IP address will be banned
|
strikes. At 10 copyright strikes your IP address will be banned
|
||||||
and you will be unable to upload files to pixeldrain. Below is a
|
and you will be unable to upload files to Nova. Below is a
|
||||||
list of files originating from your IP address which have been
|
list of files originating from your IP address which have been
|
||||||
blocked:
|
blocked:
|
||||||
</p>
|
</p>
|
||||||
@@ -151,7 +151,7 @@ onMount(async () => {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you would like to dispute your IP ban you can mail me at
|
If you would like to dispute your IP ban you can mail me at
|
||||||
support@pixeldrain.com. Please do not mail unless you have a good
|
support@nova.storage. Please do not mail unless you have a good
|
||||||
reason. If you do not provide a valid reason why the IP ban should
|
reason. If you do not provide a valid reason why the IP ban should
|
||||||
be reversed your e-mail will be ignored. And do not forget to put
|
be reversed your e-mail will be ignored. And do not forget to put
|
||||||
your IP address ({result.address}) in the e-mail.
|
your IP address ({result.address}) in the e-mail.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import OtherPlans from "./OtherPlans.svelte";
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
Pixeldrain features two different payment modes. We offer a monthly
|
Nova features two different payment modes. We offer a monthly
|
||||||
subscription which is managed by Patreon, and a Prepaid subscription
|
subscription which is managed by Patreon, and a Prepaid subscription
|
||||||
which supports a dozen different payment providers. With Prepaid you pay
|
which supports a dozen different payment providers. With Prepaid you pay
|
||||||
in advance to charge your credit, then usage will be subtracted from
|
in advance to charge your credit, then usage will be subtracted from
|
||||||
@@ -44,7 +44,7 @@ import OtherPlans from "./OtherPlans.svelte";
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
The Pro subscription is managed by Patreon. Patreon's own fees
|
The Pro subscription is managed by Patreon. Patreon's own fees
|
||||||
and sales tax will be added to this price. After paying you need
|
and sales tax will be added to this price. After paying you need
|
||||||
to link your pixeldrain account to Patreon to activate the plan.
|
to link your Nova account to Patreon to activate the plan.
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature_cell prepaid_feat">
|
<div class="feature_cell prepaid_feat">
|
||||||
@@ -224,13 +224,13 @@ import OtherPlans from "./OtherPlans.svelte";
|
|||||||
<div class="bottom_row pro_feat">
|
<div class="bottom_row pro_feat">
|
||||||
{#if window.user.subscription.id === "patreon_1"}
|
{#if window.user.subscription.id === "patreon_1"}
|
||||||
You have this plan<br/>
|
You have this plan<br/>
|
||||||
Thank you for supporting pixeldrain!
|
Thank you for supporting Nova!
|
||||||
{:else}
|
{:else}
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5736701&cadence=1" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=5736701&cadence=1" class="button button_highlight round">
|
||||||
€ 4 per month
|
€ 4 per month
|
||||||
</a>
|
</a>
|
||||||
or
|
or
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5736701&cadence=12" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=5736701&cadence=12" class="button button_highlight round">
|
||||||
€ 40 per year!
|
€ 40 per year!
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ let upload_widget
|
|||||||
<AddressReputation/>
|
<AddressReputation/>
|
||||||
|
|
||||||
<div class="page_content">
|
<div class="page_content">
|
||||||
|
<div class="highlight_yellow">
|
||||||
|
This is a development server. All files and accounts may be lost when
|
||||||
|
the final product launches.
|
||||||
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
Nova.storage is a platform for cost-effective cloud storage and
|
Nova.storage is a platform for cost-effective cloud storage and
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
Persistence<br/>
|
Persistence<br/>
|
||||||
{#if window.user.subscription.id === "patreon_2"}
|
{#if window.user.subscription.id === "patreon_2"}
|
||||||
You have this plan<br/>
|
You have this plan<br/>
|
||||||
Thank you for supporting pixeldrain!
|
Thank you for supporting Nova!
|
||||||
{:else}
|
{:else}
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291482" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=5291482" class="button button_highlight round">
|
||||||
€ 8
|
€ 8
|
||||||
</a>
|
</a>
|
||||||
/ month
|
/ month
|
||||||
@@ -30,9 +30,9 @@
|
|||||||
Tenacity<br/>
|
Tenacity<br/>
|
||||||
{#if window.user.subscription.id === "patreon_3"}
|
{#if window.user.subscription.id === "patreon_3"}
|
||||||
You have this plan<br/>
|
You have this plan<br/>
|
||||||
Thank you for supporting pixeldrain!
|
Thank you for supporting Nova!
|
||||||
{:else}
|
{:else}
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291516" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=5291516" class="button button_highlight round">
|
||||||
€ 16
|
€ 16
|
||||||
</a>
|
</a>
|
||||||
/ month
|
/ month
|
||||||
@@ -51,9 +51,9 @@
|
|||||||
Eternity<br/>
|
Eternity<br/>
|
||||||
{#if window.user.subscription.id === "patreon_4"}
|
{#if window.user.subscription.id === "patreon_4"}
|
||||||
You have this plan<br/>
|
You have this plan<br/>
|
||||||
Thank you for supporting pixeldrain!
|
Thank you for supporting Nova!
|
||||||
{:else}
|
{:else}
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=5291528" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=5291528" class="button button_highlight round">
|
||||||
€ 32
|
€ 32
|
||||||
</a>
|
</a>
|
||||||
/ month
|
/ month
|
||||||
@@ -72,9 +72,9 @@
|
|||||||
Infinity<br/>
|
Infinity<br/>
|
||||||
{#if window.user.subscription.id === "patreon_6"}
|
{#if window.user.subscription.id === "patreon_6"}
|
||||||
You have this plan<br/>
|
You have this plan<br/>
|
||||||
Thank you for supporting pixeldrain!
|
Thank you for supporting Nova!
|
||||||
{:else}
|
{:else}
|
||||||
<a href="https://www.patreon.com/join/pixeldrain/checkout?rid=6573749" class="button button_highlight round">
|
<a href="https://www.patreon.com/join/nova/checkout?rid=6573749" class="button button_highlight round">
|
||||||
€ 64
|
€ 64
|
||||||
</a>
|
</a>
|
||||||
/ month
|
/ month
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import Euro from "util/Euro.svelte";
|
import Euro from "util/Euro.svelte";
|
||||||
import ProgressBar from "util/ProgressBar.svelte";
|
import ProgressBar from "util/ProgressBar.svelte";
|
||||||
|
|
||||||
let fnx_storage = $state(0)
|
let nova_storage = $state(0)
|
||||||
let fnx_egress = $state(0)
|
let nova_egress = $state(0)
|
||||||
let fnx_total = $state(0)
|
let nova_total = $state(0)
|
||||||
let backblaze_storage = $state(0)
|
let backblaze_storage = $state(0)
|
||||||
let backblaze_egress = $state(0)
|
let backblaze_egress = $state(0)
|
||||||
let backblaze_api = $state(0)
|
let backblaze_api = $state(0)
|
||||||
@@ -20,9 +20,9 @@ let avg_file_size = $state(1000) // kB
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Nova 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)
|
nova_storage = Math.max(storage * 4, ((storage*10)/avg_file_size)*4)
|
||||||
fnx_egress = egress * 1
|
nova_egress = egress * 1
|
||||||
fnx_total = fnx_storage + fnx_egress
|
nova_total = nova_storage + nova_egress
|
||||||
|
|
||||||
// Egress at Backblaze is free up to three times the amount of storage, then
|
// Egress at Backblaze is free up to three times the amount of storage, then
|
||||||
// it's $10/TB
|
// it's $10/TB
|
||||||
@@ -35,7 +35,7 @@ $effect(() => {
|
|||||||
wasabi_storage = storage * 6.99
|
wasabi_storage = storage * 6.99
|
||||||
wasabi_total = (egress > storage) ? 0 : wasabi_storage
|
wasabi_total = (egress > storage) ? 0 : wasabi_storage
|
||||||
|
|
||||||
price_max = Math.max(fnx_total, backblaze_total, wasabi_total)
|
price_max = Math.max(nova_total, backblaze_total, wasabi_total)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -67,10 +67,10 @@ $effect(() => {
|
|||||||
<div class="bars">
|
<div class="bars">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
Nova.storage - <Euro amount={fnx_total*1e6}/> / month<br/>
|
Nova.storage - <Euro amount={nova_total*1e6}/> / month<br/>
|
||||||
<Euro amount={fnx_storage*1e6}/> storage,
|
<Euro amount={nova_storage*1e6}/> storage,
|
||||||
<Euro amount={fnx_egress*1e6}/> egress
|
<Euro amount={nova_egress*1e6}/> egress
|
||||||
<ProgressBar used={fnx_total} total={price_max}/>
|
<ProgressBar used={nova_total} total={price_max}/>
|
||||||
</div>
|
</div>
|
||||||
{#if avg_file_size < 10}
|
{#if avg_file_size < 10}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ import { onMount } from "svelte";
|
|||||||
import Discord from "icons/Discord.svelte";
|
import Discord from "icons/Discord.svelte";
|
||||||
import Github from "icons/Github.svelte";
|
import Github from "icons/Github.svelte";
|
||||||
import Mastodon from "icons/Mastodon.svelte";
|
import Mastodon from "icons/Mastodon.svelte";
|
||||||
import Patreon from "icons/Patreon.svelte";
|
|
||||||
import Reddit from "icons/Reddit.svelte";
|
|
||||||
import { formatDataVolumeBits } from "util/Formatting";
|
import { formatDataVolumeBits } from "util/Formatting";
|
||||||
import { get_endpoint, get_hostname } from "lib/PixeldrainAPI";
|
import { get_endpoint, get_hostname } from "lib/NovaAPI";
|
||||||
|
|
||||||
let { nobg = false }: {
|
let { nobg = false }: {
|
||||||
nobg?: boolean;
|
nobg?: boolean;
|
||||||
@@ -34,20 +32,14 @@ onMount(async () => {
|
|||||||
<footer class:nobg>
|
<footer class:nobg>
|
||||||
<div class="footer_content">
|
<div class="footer_content">
|
||||||
<div style="display: inline-block; margin: 0 8px;">
|
<div style="display: inline-block; margin: 0 8px;">
|
||||||
Pixeldrain is a product by
|
Nova is a product by
|
||||||
<a href="//fornaxian.tech" target="_blank" rel="noreferrer">Fornaxian Technologies</a>
|
<a href="//fornaxian.tech" target="_blank" rel="noreferrer">Fornaxian Technologies</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div style="display: inline-block; margin: 0 8px;">
|
<div style="display: inline-block; margin: 0 8px;">
|
||||||
<a href="https://www.patreon.com/pixeldrain" target="_blank" rel="noreferrer">
|
|
||||||
<Patreon style="color: var(--body_text_color);"/> Patreon
|
|
||||||
</a> |
|
|
||||||
<a href="https://discord.gg/TWKGvYAFvX" target="_blank" rel="noreferrer">
|
<a href="https://discord.gg/TWKGvYAFvX" target="_blank" rel="noreferrer">
|
||||||
<Discord style="color: var(--body_text_color);"/> Discord
|
<Discord style="color: var(--body_text_color);"/> Discord
|
||||||
</a> |
|
</a> |
|
||||||
<a href="https://reddit.com/r/pixeldrain" target="_blank" rel="noreferrer">
|
|
||||||
<Reddit style="color: var(--body_text_color);"/> Reddit
|
|
||||||
</a> |
|
|
||||||
<a href="https://github.com/Fornaxian" target="_blank" rel="noreferrer">
|
<a href="https://github.com/Fornaxian" target="_blank" rel="noreferrer">
|
||||||
<Github style="color: var(--body_text_color);"/> GitHub
|
<Github style="color: var(--body_text_color);"/> GitHub
|
||||||
</a> |
|
</a> |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PickAmount from "./PickAmount.svelte";
|
|||||||
import PickCountry from "./PickCountry.svelte";
|
import PickCountry from "./PickCountry.svelte";
|
||||||
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
||||||
import PickName from "./PickName.svelte";
|
import PickName from "./PickName.svelte";
|
||||||
import { get_misc_vat_rate, get_user } from "lib/PixeldrainAPI";
|
import { get_misc_vat_rate, get_user } from "lib/NovaAPI";
|
||||||
import { countries } from "country-data-list";
|
import { countries } from "country-data-list";
|
||||||
import PickProvider from "./PickProvider.svelte";
|
import PickProvider from "./PickProvider.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { countries } from "country-data-list";
|
import type { countries } from "country-data-list";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
export type CheckoutState = {
|
export type CheckoutState = {
|
||||||
country: typeof countries.all[0]
|
country: typeof countries.all[0]
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const submit = async (e: SubmitEvent) => {
|
|||||||
</form>
|
</form>
|
||||||
<hr/>
|
<hr/>
|
||||||
<p style="text-align: initial;">
|
<p style="text-align: initial;">
|
||||||
This Pixeldrain premium plan costs €1 per month, but due to
|
This Nova premium plan costs €1 per month, but due to
|
||||||
processing fees we can't accept payments less than €10. So your
|
processing fees we can't accept payments less than €10. So your
|
||||||
deposit will give roughly 10 months of premium service depending on
|
deposit will give roughly 10 months of premium service depending on
|
||||||
usage. You can track your spending on the <a
|
usage. You can track your spending on the <a
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { preventDefault } from 'svelte/legacy';
|
import { preventDefault } from 'svelte/legacy';
|
||||||
import { countries } from "country-data-list";
|
import { countries } from "country-data-list";
|
||||||
import { get_misc_vat_rate, put_user } from "lib/PixeldrainAPI";
|
import { get_misc_vat_rate, put_user } from "lib/NovaAPI";
|
||||||
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
import { payment_providers, type CheckoutState } from "./CheckoutLib";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { put_user } from "lib/PixeldrainAPI";
|
import { put_user } from "lib/NovaAPI";
|
||||||
import { type CheckoutState } from "./CheckoutLib";
|
import { type CheckoutState } from "./CheckoutLib";
|
||||||
|
|
||||||
let { status = $bindable() }: {
|
let { status = $bindable() }: {
|
||||||
@@ -35,11 +35,10 @@ const submit = async (e: SubmitEvent) => {
|
|||||||
</form>
|
</form>
|
||||||
<hr/>
|
<hr/>
|
||||||
<p style="text-align: initial;">
|
<p style="text-align: initial;">
|
||||||
This Pixeldrain premium plan costs €1 per month, but due to
|
This Nova premium plan costs €1 per month, but due to processing fees we
|
||||||
processing fees we can't accept payments less than €10. So your
|
can't accept payments less than €10. So your deposit will give roughly 10
|
||||||
deposit will give roughly 10 months of premium service depending on
|
months of premium service depending on usage. You can track your spending on
|
||||||
usage. You can track your spending on the <a
|
the <a href="/user/prepaid/transactions">transactions page</a>.
|
||||||
href="/user/prepaid/transactions">transactions page</a>.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { put_user } from "lib/PixeldrainAPI";
|
import { put_user } from "lib/NovaAPI";
|
||||||
import { payment_providers, type CheckoutState, type PaymentProvider } from "./CheckoutLib";
|
import { payment_providers, type CheckoutState, type PaymentProvider } from "./CheckoutLib";
|
||||||
|
|
||||||
let { status = $bindable() }: {
|
let { status = $bindable() }: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { countries } from "country-data-list"
|
import { countries } from "country-data-list"
|
||||||
import { check_response, get_endpoint } from "./PixeldrainAPI"
|
import { check_response, get_endpoint } from "./NovaAPI"
|
||||||
|
|
||||||
export const country_name = (country: string) => {
|
export const country_name = (country: string) => {
|
||||||
if (country !== "" && countries[country] !== undefined) {
|
if (country !== "" && countries[country] !== undefined) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { fs_check_response, fs_path_url, type FSNode } from "./FilesystemAPI.svelte"
|
import { fs_check_response, fs_path_url, type FSNode } from "./FilesystemAPI.svelte"
|
||||||
import { loading_finish, loading_start } from "lib/Loading"
|
import { loading_finish, loading_start } from "lib/Loading"
|
||||||
import { get_user } from "lib/PixeldrainAPI"
|
import { get_user } from "lib/NovaAPI"
|
||||||
|
|
||||||
const bookmarks_file = "/me/.fnx/bookmarks.json"
|
const bookmarks_file = "/me/.nova/bookmarks.json"
|
||||||
|
|
||||||
export type Bookmark = {
|
export type Bookmark = {
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
@@ -230,6 +230,29 @@ export const fs_delete_all = async (path: string) => {
|
|||||||
) as GenericResponse
|
) as GenericResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fs_trash = async (path: string) => {
|
||||||
|
return await fs_check_response(
|
||||||
|
await fetch(fs_path_url(path) + "?trash", { method: "DELETE" })
|
||||||
|
) as GenericResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TrashEntry = {
|
||||||
|
node: FSNode;
|
||||||
|
original_path: string;
|
||||||
|
deletion_date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fs_get_trash = async (path: string) => {
|
||||||
|
let resp = await fs_check_response(
|
||||||
|
await fetch(fs_path_url(path) + "?trash")
|
||||||
|
) as TrashEntry[]
|
||||||
|
resp.forEach((entry, index) => {
|
||||||
|
resp[index].node = Object.assign(new FSNode(), entry.node)
|
||||||
|
})
|
||||||
|
|
||||||
|
return resp
|
||||||
|
};
|
||||||
|
|
||||||
export const fs_search = async (path: string, term: string, limit = 10) => {
|
export const fs_search = async (path: string, term: string, limit = 10) => {
|
||||||
return await fs_check_response(
|
return await fs_check_response(
|
||||||
await fetch(
|
await fetch(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { get_user, type Subscription, type User } from "./PixeldrainAPI";
|
import { get_user, type Subscription, type User } from "./NovaAPI";
|
||||||
|
|
||||||
export const user = writable(
|
export const user = writable(
|
||||||
{ subscription: {} as Subscription } as User,
|
{ subscription: {} as Subscription } as User,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import Form, { type FormConfig } from "util/Form.svelte"
|
import Form, { type FormConfig } from "util/Form.svelte"
|
||||||
import { check_response, get_endpoint } from "lib/PixeldrainAPI";
|
import { check_response, get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
let dispatch = createEventDispatcher()
|
let dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Form, { type FormConfig } from "util/Form.svelte"
|
import Form, { type FormConfig } from "util/Form.svelte"
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
let form: FormConfig = {
|
let form: FormConfig = {
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import TabMenu from "util/TabMenu.svelte";
|
|||||||
import Register from "./Register.svelte";
|
import Register from "./Register.svelte";
|
||||||
import Login from "./Login.svelte";
|
import Login from "./Login.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { get_user } from "lib/PixeldrainAPI";
|
import { get_user } from "lib/NovaAPI";
|
||||||
|
|
||||||
let pages = [
|
let pages = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const reload_sheet = () => {
|
|||||||
<div id="page_content" class="page_content">
|
<div id="page_content" class="page_content">
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
You can change how pixeldrain looks! Your theme choice will
|
You can change how Nova looks! Your theme choice will
|
||||||
be saved in a cookie.
|
be saved in a cookie.
|
||||||
</p>
|
</p>
|
||||||
<h2>Theme</h2>
|
<h2>Theme</h2>
|
||||||
@@ -100,9 +100,9 @@ const reload_sheet = () => {
|
|||||||
Classic 2022 style, with purple gradients
|
Classic 2022 style, with purple gradients
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<input type="radio" id="style_classic" name="style"><label for="style_classic">Pixeldrain classic (gray)</label>
|
<input type="radio" id="style_classic" name="style"><label for="style_classic">Nova classic (gray)</label>
|
||||||
<br/>
|
<br/>
|
||||||
Classic pre-2020 pixeldrain style, dark gray
|
Classic pre-2020 Nova style, dark gray
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
Other (experimental) themes
|
Other (experimental) themes
|
||||||
@@ -126,9 +126,9 @@ const reload_sheet = () => {
|
|||||||
<br/>
|
<br/>
|
||||||
<input type="radio" id="style_adwaita_light" name="style"><label for="style_adwaita_light">Adwaita light</label>
|
<input type="radio" id="style_adwaita_light" name="style"><label for="style_adwaita_light">Adwaita light</label>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<input type="radio" id="style_pixeldrain98" name="style"><label for="style_pixeldrain98">Pixeldrain 98</label>
|
<input type="radio" id="style_nova98" name="style"><label for="style_nova98">Nova 98</label>
|
||||||
<br/>
|
<br/>
|
||||||
<input type="radio" id="style_pixeldrain98_dark" name="style"><label for="style_pixeldrain98_dark">Pixeldrain 98 (dark)</label>
|
<input type="radio" id="style_nova98_dark" name="style"><label for="style_nova98_dark">Nova 98 (dark)</label>
|
||||||
|
|
||||||
<h2>Hue</h2>
|
<h2>Hue</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Speedtest from "./Speedtest.svelte";
|
|||||||
<section>
|
<section>
|
||||||
<h2>How does this work?</h2>
|
<h2>How does this work?</h2>
|
||||||
<p>
|
<p>
|
||||||
The speedtest measures the maximum download speed from pixeldrain's
|
The speedtest measures the maximum download speed from Nova's
|
||||||
servers to your computer. This speed is not affected by the daily
|
servers to your computer. This speed is not affected by the daily
|
||||||
download limit for free users.
|
download limit for free users.
|
||||||
</p>
|
</p>
|
||||||
@@ -45,8 +45,8 @@ import Speedtest from "./Speedtest.svelte";
|
|||||||
byte.
|
byte.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The third number shows the latency to the pixeldrain servers. This
|
The third number shows the latency to the Nova servers. This
|
||||||
is dependent on how far you are removed from the closest pixeldrain
|
is dependent on how far you are removed from the closest Nova
|
||||||
server. The lower the latency the faster your downloads will be,
|
server. The lower the latency the faster your downloads will be,
|
||||||
generally. The number shows request latency and not ping latency.
|
generally. The number shows request latency and not ping latency.
|
||||||
HTTP requests have some overhead which means this latency number
|
HTTP requests have some overhead which means this latency number
|
||||||
@@ -65,7 +65,7 @@ import Speedtest from "./Speedtest.svelte";
|
|||||||
because it stays within their network.
|
because it stays within their network.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Pixeldrain does not have this luxury. Because our budget is very
|
Nova does not have this luxury. Because our budget is very
|
||||||
small we are only able to afford the cheapest bandwidth available.
|
small we are only able to afford the cheapest bandwidth available.
|
||||||
This means that the data has to travel further and is more likely to
|
This means that the data has to travel further and is more likely to
|
||||||
be throttled.
|
be throttled.
|
||||||
@@ -85,11 +85,11 @@ import Speedtest from "./Speedtest.svelte";
|
|||||||
</p>
|
</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
Your ISP is limiting the connection speed to pixeldrain's
|
Your ISP is limiting the connection speed to Nova's
|
||||||
servers
|
servers
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The pixeldrain servers are overloaded
|
The Nova servers are overloaded
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -147,11 +147,11 @@ const logout = async (key) => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="1">
|
<td colspan="1">
|
||||||
{#if row.app_name === "website login"}
|
{#if row.app_name === "website login"}
|
||||||
<img src="/res/img/pixeldrain_32.png" alt="Pixeldrain logo" class="app_icon"/>
|
<img src="/res/img/nova_32.png" alt="Nova logo" class="app_icon"/>
|
||||||
Pixeldrain website
|
Nova website
|
||||||
{:else if row.app_name === "website keys page"}
|
{:else if row.app_name === "website keys page"}
|
||||||
<i class="icon">vpn_key</i>
|
<i class="icon">vpn_key</i>
|
||||||
Pixeldrain keys page
|
Nova keys page
|
||||||
{:else if row.app_name === "sharex"}
|
{:else if row.app_name === "sharex"}
|
||||||
<img src="/res/img/sharex.png" alt="ShareX logo" class="app_icon"/>
|
<img src="/res/img/sharex.png" alt="ShareX logo" class="app_icon"/>
|
||||||
ShareX
|
ShareX
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CopyButton from "layout/CopyButton.svelte";
|
|||||||
import Form from "util/Form.svelte";
|
import Form from "util/Form.svelte";
|
||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import OtpSetup from "./OTPSetup.svelte";
|
import OtpSetup from "./OTPSetup.svelte";
|
||||||
import { put_user } from "lib/PixeldrainAPI";
|
import { put_user } from "lib/NovaAPI";
|
||||||
|
|
||||||
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
|
let affiliate_link = window.location.protocol+"//"+window.location.host + "?ref=" + encodeURIComponent(window.user.username)
|
||||||
let affiliate_deny = $state(false)
|
let affiliate_deny = $state(false)
|
||||||
@@ -114,7 +114,7 @@ const affiliate_settings = {
|
|||||||
type: "text",
|
type: "text",
|
||||||
default_value: window.user.affiliate_user_name,
|
default_value: window.user.affiliate_user_name,
|
||||||
description: `The affiliate user name can be the name of a
|
description: `The affiliate user name can be the name of a
|
||||||
pixeldrain account you wish to support with your subscription.
|
Nova account you wish to support with your subscription.
|
||||||
The account will receive a fee of €0.50 for every month that
|
The account will receive a fee of €0.50 for every month that
|
||||||
your premium plan is active. This does not cost you anything
|
your premium plan is active. This does not cost you anything
|
||||||
extra.`,
|
extra.`,
|
||||||
@@ -142,7 +142,7 @@ let delete_account = {
|
|||||||
type: "description",
|
type: "description",
|
||||||
description: `
|
description: `
|
||||||
<p>
|
<p>
|
||||||
When you delete your pixeldrain account you will be
|
When you delete your Nova account you will be
|
||||||
logged out on all of your devices. Your account will be
|
logged out on all of your devices. Your account will be
|
||||||
scheduled for deletion in seven days. If you log back in to your
|
scheduled for deletion in seven days. If you log back in to your
|
||||||
account during those seven days the deletion will be canceled.
|
account during those seven days the deletion will be canceled.
|
||||||
@@ -161,7 +161,7 @@ let delete_account = {
|
|||||||
<p>
|
<p>
|
||||||
If you have an active Pro subscription you need to end that
|
If you have an active Pro subscription you need to end that
|
||||||
separately through your Patreon account. Deleting your
|
separately through your Patreon account. Deleting your
|
||||||
pixeldrain account will not cancel the subscription.
|
Nova account will not cancel the subscription.
|
||||||
</p>`,
|
</p>`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -200,14 +200,14 @@ let delete_account = {
|
|||||||
Your own affiliate link is
|
Your own affiliate link is
|
||||||
<a href="{affiliate_link}">{affiliate_link}</a>
|
<a href="{affiliate_link}">{affiliate_link}</a>
|
||||||
<CopyButton small_icon text={affiliate_link}/>. Share this link
|
<CopyButton small_icon text={affiliate_link}/>. Share this link
|
||||||
with premium pixeldrain users to gain commissions. You can use
|
with premium Nova users to gain commissions. You can use
|
||||||
the "?ref={encodeURIComponent(window.user.username)}" referral
|
the "?ref={encodeURIComponent(window.user.username)}" referral
|
||||||
code on download pages too. For a detailed description of the
|
code on download pages too. For a detailed description of the
|
||||||
affiliate program please check out the <a
|
affiliate program please check out the <a
|
||||||
href="/about#toc_12">Q&A page</a>.
|
href="/about#toc_12">Q&A page</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Note that the link includes the name of your pixeldrain
|
Note that the link includes the name of your Nova
|
||||||
account. If you change your account name the link will stop
|
account. If you change your account name the link will stop
|
||||||
working and you might stop receiving commissions.
|
working and you might stop receiving commissions.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Modal from "util/Modal.svelte";
|
import Modal from "util/Modal.svelte";
|
||||||
import { get_user, put_user } from "lib/PixeldrainAPI";
|
import { get_user, put_user } from "lib/NovaAPI";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
|
|
||||||
let { always = false }: {
|
let { always = false }: {
|
||||||
@@ -74,8 +74,8 @@ const deny = () => {
|
|||||||
<Modal bind:this={modal} title="Affiliate sponsoring request" width="700px">
|
<Modal bind:this={modal} title="Affiliate sponsoring request" width="700px">
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
Hi! {referral} wants you to sponsor their pixeldrain account. This
|
Hi! {referral} wants you to sponsor their Nova account. This
|
||||||
will give them €0.50 every month in pixeldrain prepaid credit. They
|
will give them €0.50 every month in Nova prepaid credit. They
|
||||||
can use this credit to get a discount on their file storage and
|
can use this credit to get a discount on their file storage and
|
||||||
sharing costs. Here is a short summary of what this entails:
|
sharing costs. Here is a short summary of what this entails:
|
||||||
</p>
|
</p>
|
||||||
@@ -86,12 +86,12 @@ const deny = () => {
|
|||||||
longer receive commissions.
|
longer receive commissions.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Pixeldrain credit cannot be cashed out. So they are not earning
|
Nova credit cannot be cashed out. So they are not earning
|
||||||
real money with this.
|
real money with this.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
This does not cost you any extra money. The commissions paid out
|
This does not cost you any extra money. The commissions paid out
|
||||||
to the creator are paid for by pixeldrain itself.
|
to the creator are paid for by Nova itself.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
You can change who you are sponsoring at any time on your <a
|
You can change who you are sponsoring at any time on your <a
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ onMount(() => {
|
|||||||
<p>
|
<p>
|
||||||
This setting only applies to your account, others will still see the
|
This setting only applies to your account, others will still see the
|
||||||
download page when visiting your files. When this is enabled you will be
|
download page when visiting your files. When this is enabled you will be
|
||||||
redirected to the file download API when clicking a pixeldrain link.
|
redirected to the file download API when clicking a Nova link.
|
||||||
This means that images, videos and PDF files will be opened directly in
|
This means that images, videos and PDF files will be opened directly in
|
||||||
your browser, and other files are immediately downloaded to your device.
|
your browser, and other files are immediately downloaded to your device.
|
||||||
This only works for single file links, not albums. You will need a Pro
|
This only works for single file links, not albums. You will need a Pro
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ onMount(() => {
|
|||||||
<h2>
|
<h2>
|
||||||
Connect
|
Connect
|
||||||
<img src="/res/img/jdownloader.png" alt="JDownloader logo" class="app_icon_small"/>
|
<img src="/res/img/jdownloader.png" alt="JDownloader logo" class="app_icon_small"/>
|
||||||
JDownloader to your pixeldrain account
|
JDownloader to your Nova account
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
JDownloader is a software program which makes it easier to download
|
JDownloader is a software program which makes it easier to download
|
||||||
things from the web. You can connect JDownloader to your pixeldrain
|
things from the web. You can connect JDownloader to your Nova
|
||||||
account to benefit from faster download speed and other pixeldrain
|
account to benefit from faster download speed and other Nova Pro
|
||||||
Pro features.
|
features.
|
||||||
</p>
|
</p>
|
||||||
<h3>Step 1: Install JDownloader</h3>
|
<h3>Step 1: Install JDownloader</h3>
|
||||||
<p>
|
<p>
|
||||||
@@ -81,11 +81,11 @@ onMount(() => {
|
|||||||
</div>
|
</div>
|
||||||
<h3>Step 3: Generate an app key</h3>
|
<h3>Step 3: Generate an app key</h3>
|
||||||
<p>
|
<p>
|
||||||
To connect JDownloader to pixeldrain you need to generate an API
|
To connect JDownloader to Nova you need to generate an API
|
||||||
key and enter it in JDownloader's Account Manager.
|
key and enter it in JDownloader's Account Manager.
|
||||||
<br/>
|
<br/>
|
||||||
<strong>Do not show the generated key to anyone</strong>, it can
|
<strong>Do not show the generated key to anyone</strong>, it can
|
||||||
be used to gain access to your pixeldrain account!
|
be used to gain access to your Nova account!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{#if !api_key}
|
{#if !api_key}
|
||||||
@@ -121,46 +121,46 @@ onMount(() => {
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Click Save and you're done! You can now download files from
|
Click Save and you're done! You can now download files from
|
||||||
pixeldrain with JDownloader.
|
Nova with JDownloader.
|
||||||
</p>
|
</p>
|
||||||
{:else if app_name === "sharex"}
|
{:else if app_name === "sharex"}
|
||||||
<h2>
|
<h2>
|
||||||
Connect
|
Connect
|
||||||
<img src="/res/img/sharex.png" alt="ShareX logo" class="app_icon_small"/>
|
<img src="/res/img/sharex.png" alt="ShareX logo" class="app_icon_small"/>
|
||||||
ShareX to your pixeldrain account
|
ShareX to your Nova account
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
ShareX is a Screen capture, file sharing and productivity tool.
|
ShareX is a Screen capture, file sharing and productivity tool.
|
||||||
Pixeldrain is supported as a custom uploader. You can <a
|
Nova is supported as a custom uploader. You can <a
|
||||||
href="https://getsharex.com/" target="_blank" rel="noreferrer">get
|
href="https://getsharex.com/" target="_blank" rel="noreferrer">get
|
||||||
ShareX here</a>.
|
ShareX here</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Here you can download our custom ShareX uploader which uses
|
Here you can download our custom ShareX uploader which uses
|
||||||
pixeldrain to upload your files. This uploader is configured to
|
Nova to upload your files. This uploader is configured to
|
||||||
upload files to your personal pixeldrain account. <strong>Do not
|
upload files to your personal Nova account. <strong>Do not
|
||||||
share the configuration file with anyone</strong>, it contains
|
share the configuration file with anyone</strong>, it contains
|
||||||
your account credentials.
|
your account credentials.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<a href="/misc/sharex/pixeldrain.com.sxcu" class="button button_highlight">
|
<a href="/misc/sharex/nova.storage.sxcu" class="button button_highlight">
|
||||||
<i class="icon small">save</i>
|
<i class="icon small">save</i>
|
||||||
Download ShareX Uploader
|
Download ShareX Uploader
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Setting pixeldrain as default uploader</h3>
|
<h3>Setting Nova as default uploader</h3>
|
||||||
<p>
|
<p>
|
||||||
Download the uploader config and choose 'Open file'
|
Download the uploader config and choose 'Open file'
|
||||||
<br/>
|
<br/>
|
||||||
<img src="/res/img/sharex_download.png" style="max-width: 100%;" alt=""/><br/>
|
<img src="/res/img/sharex_download.png" style="max-width: 100%;" alt=""/><br/>
|
||||||
Set pixeldrain.com as active uploader. Choose Yes
|
Set nova.storage as active uploader. Choose Yes
|
||||||
<br/>
|
<br/>
|
||||||
<img src="/res/img/sharex_default.png" style="max-width: 100%;" alt=""/><br/>
|
<img src="/res/img/sharex_default.png" style="max-width: 100%;" alt=""/><br/>
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<h2>Connect an app to your pixeldrain account</h2>
|
<h2>Connect an app to your Nova account</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="?app=jdownloader" class="button">
|
<a href="?app=jdownloader" class="button">
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ onMount(() => {
|
|||||||
<section>
|
<section>
|
||||||
<h2 id="deposit">Deposit credit</h2>
|
<h2 id="deposit">Deposit credit</h2>
|
||||||
<p>
|
<p>
|
||||||
Pixeldrain credit can be used for our Prepaid subscription plan, which
|
Nova credit can be used for our Prepaid subscription plan, which
|
||||||
is different from the Patreon plans. Instead of monthly limits, with
|
is different from the Patreon plans. Instead of monthly limits, with
|
||||||
Prepaid there are no limits. You pay for what you use, at a rate of €4
|
Prepaid there are no limits. You pay for what you use, at a rate of €4
|
||||||
per TB per month for storage and €1 per TB for data transfer. Your
|
per TB per month for storage and €1 per TB for data transfer. Your
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ onMount(() => {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The list should be formatted as a list of domain names separated by a
|
The list should be formatted as a list of domain names separated by a
|
||||||
space. Like this: 'pixeldrain.com google.com twitter.com'
|
space. Like this: 'nova.storage google.com twitter.com'
|
||||||
</p>
|
</p>
|
||||||
Domain names:<br/>
|
Domain names:<br/>
|
||||||
<form class="form_row" onsubmit={preventDefault(save_embed)}>
|
<form class="form_row" onsubmit={preventDefault(save_embed)}>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ let frac = $derived(used / total)
|
|||||||
<p>
|
<p>
|
||||||
You have used all of your data cap. People can still download your
|
You have used all of your data cap. People can still download your
|
||||||
files, but premium features are disabled. This means that the
|
files, but premium features are disabled. This means that the
|
||||||
download page shows pixeldrain branding, people who download your
|
download page shows Nova branding, people who download your
|
||||||
files have a daily download limit and hotlinking is disabled.
|
files have a daily download limit and hotlinking is disabled.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ let frac = $derived(used / total)
|
|||||||
<p>
|
<p>
|
||||||
You have used {(frac*100).toFixed(0)}% of your data cap. If your
|
You have used {(frac*100).toFixed(0)}% of your data cap. If your
|
||||||
data runs out the premium features related to downloading will be
|
data runs out the premium features related to downloading will be
|
||||||
disabled. This means that the download page shows pixeldrain
|
disabled. This means that the download page shows Nova
|
||||||
branding, people who download your files have a daily download limit
|
branding, people who download your files have a daily download limit
|
||||||
and hotlinking is disabled.
|
and hotlinking is disabled.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { onMount } from "svelte";
|
|||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import CopyButton from "layout/CopyButton.svelte";
|
import CopyButton from "layout/CopyButton.svelte";
|
||||||
import ToggleButton from "layout/ToggleButton.svelte";
|
import ToggleButton from "layout/ToggleButton.svelte";
|
||||||
import { check_response, get_endpoint, get_user, type User } from "lib/PixeldrainAPI";
|
import { check_response, get_endpoint, get_user, type User } from "lib/NovaAPI";
|
||||||
|
|
||||||
let user: User = $state(null)
|
let user: User = $state(null)
|
||||||
let secret = $state("")
|
let secret = $state("")
|
||||||
|
|||||||
@@ -31,23 +31,23 @@ onMount(() => {
|
|||||||
{#if patreon_result !== ""}
|
{#if patreon_result !== ""}
|
||||||
{#if patreon_error === "patreon_authentication_denied"}
|
{#if patreon_error === "patreon_authentication_denied"}
|
||||||
<div class="highlight_yellow">
|
<div class="highlight_yellow">
|
||||||
Please press "Allow" when asked if pixeldrain can access your
|
Please press "Allow" when asked if Nova can access your
|
||||||
profile.
|
profile.
|
||||||
</div>
|
</div>
|
||||||
{:else if patreon_result === "error"}
|
{:else if patreon_result === "error"}
|
||||||
<div class="highlight_red">
|
<div class="highlight_red">
|
||||||
<p>
|
<p>
|
||||||
An error occurred while linking Patreon subscription. Check if
|
An error occurred while linking Patreon subscription. Check if
|
||||||
there are any Pixeldrain integrations under "Logged in with
|
there are any Nova integrations under "Logged in with
|
||||||
Patreon" on this page: <a
|
Patreon" on this page: <a
|
||||||
href="https://www.patreon.com/settings/apps">https://www.patreon.com/settings/apps</a>.
|
href="https://www.patreon.com/settings/apps">https://www.patreon.com/settings/apps</a>.
|
||||||
Try disconnecting all Pixeldrain logins and try again.
|
Try disconnecting all Nova logins and try again.
|
||||||
</p>
|
</p>
|
||||||
<li>
|
<li>
|
||||||
This can also happen if you canceled your Patreon membership
|
This can also happen if you canceled your Patreon membership
|
||||||
before upgrading your pixeldrain account. In that case, go to
|
before upgrading your Nova account. In that case, go to
|
||||||
the <a
|
the <a
|
||||||
href="https://www.patreon.com/pixeldrain/membership">memberships
|
href="https://www.patreon.com/nova/membership">memberships
|
||||||
page</a> and activate your membership again. If you already paid
|
page</a> and activate your membership again. If you already paid
|
||||||
in the last 30 days you will not be charged again.
|
in the last 30 days you will not be charged again.
|
||||||
</li>
|
</li>
|
||||||
@@ -88,17 +88,17 @@ onMount(() => {
|
|||||||
You pledged without selecting a support tier. It happens
|
You pledged without selecting a support tier. It happens
|
||||||
sometimes that people pledge an amount of money without
|
sometimes that people pledge an amount of money without
|
||||||
selecting a support tier. If you don't have a tier
|
selecting a support tier. If you don't have a tier
|
||||||
pixeldrain won't know which subscription you should get,
|
Nova won't know which subscription you should get,
|
||||||
regardless of how much you paid. To fix this you can select
|
regardless of how much you paid. To fix this you can select
|
||||||
a tier from <a
|
a tier from <a
|
||||||
href="https://www.patreon.com/pixeldrain/membership">the
|
href="https://www.patreon.com/nova/membership">the
|
||||||
memberships page</a> and proceed to checkout. If you already
|
memberships page</a> and proceed to checkout. If you already
|
||||||
pledged the right amount you will not have to pay again.
|
pledged the right amount you will not have to pay again.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Or you have not pledged at all yet. In that case it's
|
Or you have not pledged at all yet. In that case it's
|
||||||
simple: <a
|
simple: <a
|
||||||
href="https://www.patreon.com/pixeldrain/membership">go to
|
href="https://www.patreon.com/nova/membership">go to
|
||||||
Patreon</a> and purchase a membership.
|
Patreon</a> and purchase a membership.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
@@ -110,7 +110,7 @@ onMount(() => {
|
|||||||
</div>
|
</div>
|
||||||
{:else if patreon_result === "success"}
|
{:else if patreon_result === "success"}
|
||||||
<div class="highlight_green">
|
<div class="highlight_green">
|
||||||
Success! Your Patreon pledge has been linked to your pixeldrain
|
Success! Your Patreon pledge has been linked to your Nova
|
||||||
account. You are now able to use Pro features.
|
account. You are now able to use Pro features.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import EmbeddingControls from "./EmbeddingControls.svelte";
|
|||||||
import Dashboard from "./dashboard/Dashboard.svelte";
|
import Dashboard from "./dashboard/Dashboard.svelte";
|
||||||
import AffiliatePrompt from "./AffiliatePrompt.svelte";
|
import AffiliatePrompt from "./AffiliatePrompt.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { get_user, type User } from "lib/PixeldrainAPI";
|
import { get_user, type User } from "lib/NovaAPI";
|
||||||
|
|
||||||
let pages: Tab[] = [
|
let pages: Tab[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import SuccessMessage from "util/SuccessMessage.svelte";
|
|||||||
import PatreonActivationResult from "./PatreonActivationResult.svelte";
|
import PatreonActivationResult from "./PatreonActivationResult.svelte";
|
||||||
import { loading_finish, loading_start } from "lib/Loading";
|
import { loading_finish, loading_start } from "lib/Loading";
|
||||||
import { user } from "lib/UserStore";
|
import { user } from "lib/UserStore";
|
||||||
import { put_user } from "lib/PixeldrainAPI";
|
import { put_user } from "lib/NovaAPI";
|
||||||
|
|
||||||
let subscription = $state($user.subscription.id)
|
let subscription = $state($user.subscription.id)
|
||||||
let subscription_type = $state($user.subscription.type)
|
let subscription_type = $state($user.subscription.type)
|
||||||
@@ -31,7 +31,7 @@ const update = async (plan: string) => {
|
|||||||
<div class="highlight_green">
|
<div class="highlight_green">
|
||||||
<h2>Payment successful!</h2>
|
<h2>Payment successful!</h2>
|
||||||
<p>
|
<p>
|
||||||
Thank you for supporting pixeldrain! The credit has been added
|
Thank you for supporting Nova! The credit has been added
|
||||||
to your account balance.
|
to your account balance.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -42,7 +42,7 @@ const update = async (plan: string) => {
|
|||||||
before your credit is deposited. SEPA transfers can take up to
|
before your credit is deposited. SEPA transfers can take up to
|
||||||
two working days for example. When the deposit is complete you
|
two working days for example. When the deposit is complete you
|
||||||
will receive an e-mail. If it takes too long, contact
|
will receive an e-mail. If it takes too long, contact
|
||||||
support@pixeldrain.com.
|
support@nova.storage.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{:else if window.location.hash === "#order_expired"}
|
{:else if window.location.hash === "#order_expired"}
|
||||||
@@ -72,10 +72,10 @@ const update = async (plan: string) => {
|
|||||||
Here you can switch between different subscription plans.
|
Here you can switch between different subscription plans.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The Patreon subscription is managed by Patreon. Pixeldrain cannot modify
|
The Patreon subscription is managed by Patreon. Nova cannot modify
|
||||||
or end your subsciption. If you would like to cancel your Patreon plan
|
or end your subsciption. If you would like to cancel your Patreon plan
|
||||||
you can do that <a
|
you can do that <a
|
||||||
href="https://www.patreon.com/settings/memberships/pixeldrain"
|
href="https://www.patreon.com/settings/memberships/nova"
|
||||||
target="_blank">on Patreon</a>.
|
target="_blank">on Patreon</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -96,7 +96,7 @@ const update = async (plan: string) => {
|
|||||||
Patreon<br/>
|
Patreon<br/>
|
||||||
{#if subscription_type === "patreon"}
|
{#if subscription_type === "patreon"}
|
||||||
Currently active<br/>
|
Currently active<br/>
|
||||||
<a class="button" href="https://www.patreon.com/settings/memberships/pixeldrain" target="_blank">
|
<a class="button" href="https://www.patreon.com/settings/memberships/nova" target="_blank">
|
||||||
<i class="icon">settings</i>
|
<i class="icon">settings</i>
|
||||||
Manage
|
Manage
|
||||||
</a>
|
</a>
|
||||||
@@ -110,7 +110,7 @@ const update = async (plan: string) => {
|
|||||||
<div class="feat_normal round_tr" class:feat_highlight={subscription_type === "patreon"}>
|
<div class="feat_normal round_tr" class:feat_highlight={subscription_type === "patreon"}>
|
||||||
<p>
|
<p>
|
||||||
This subscription is managed by Patreon. You will need to <a
|
This subscription is managed by Patreon. You will need to <a
|
||||||
href="https://www.patreon.com/pixeldrain/membership"
|
href="https://www.patreon.com/nova/membership"
|
||||||
target="_blank">purchase a plan on Patreon</a> before you
|
target="_blank">purchase a plan on Patreon</a> before you
|
||||||
can activate this subscription. After your purchase you can
|
can activate this subscription. After your purchase you can
|
||||||
click the "Link Patreon" button and your account will be
|
click the "Link Patreon" button and your account will be
|
||||||
@@ -144,7 +144,7 @@ const update = async (plan: string) => {
|
|||||||
plan. If you currently have a Patreon subscription active,
|
plan. If you currently have a Patreon subscription active,
|
||||||
then enabling prepaid will not cancel that subscription. You
|
then enabling prepaid will not cancel that subscription. You
|
||||||
can end your subscription <a
|
can end your subscription <a
|
||||||
href="https://www.patreon.com/settings/memberships/pixeldrain"
|
href="https://www.patreon.com/settings/memberships/nova"
|
||||||
target="_blank">on Patreon.com</a>.
|
target="_blank">on Patreon.com</a>.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ onMount(() => {
|
|||||||
of days in a month (30.4375).
|
of days in a month (30.4375).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Example: If you have 2 TB stored on your pixeldrain account at €4 per TB
|
Example: If you have 2 TB stored on your Nova account at €4 per TB
|
||||||
then the daily charge will be:<br/>
|
then the daily charge will be:<br/>
|
||||||
|
|
||||||
2 TB * € 4 = € 8<br/>
|
2 TB * € 4 = € 8<br/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Button from "layout/Button.svelte";
|
import Button from "layout/Button.svelte";
|
||||||
import { logout_user } from "lib/PixeldrainAPI";
|
import { logout_user } from "lib/NovaAPI";
|
||||||
</script>
|
</script>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Username: {window.user.username}</li>
|
<li>Username: {window.user.username}</li>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { onMount } from "svelte";
|
|||||||
import Chart from "util/Chart.svelte";
|
import Chart from "util/Chart.svelte";
|
||||||
import { color_by_name } from "util/Util";
|
import { color_by_name } from "util/Util";
|
||||||
import { formatDataVolume, formatThousands } from "util/Formatting";
|
import { formatDataVolume, formatThousands } from "util/Formatting";
|
||||||
import { get_endpoint } from "lib/PixeldrainAPI";
|
import { get_endpoint } from "lib/NovaAPI";
|
||||||
|
|
||||||
let { card_size = 1 }: {
|
let { card_size = 1 }: {
|
||||||
card_size?: number;
|
card_size?: number;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import { formatDataVolume } from "util/Formatting";
|
|||||||
{#if window.user.subscription.id !== ""}
|
{#if window.user.subscription.id !== ""}
|
||||||
<li>
|
<li>
|
||||||
Support: For questions related to your account you can send a
|
Support: For questions related to your account you can send a
|
||||||
message to support@pixeldrain.com
|
message to support@nova.storage
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { get_endpoint, get_user, type User } from "lib/PixeldrainAPI";
|
import { get_endpoint, get_user, type User } from "lib/NovaAPI";
|
||||||
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";
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export type SubmitResult = {
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Spinner from "./Spinner.svelte";
|
import Spinner from "./Spinner.svelte";
|
||||||
import type { GenericResponse } from "lib/PixeldrainAPI";
|
import type { GenericResponse } from "lib/NovaAPI";
|
||||||
|
|
||||||
let { config }: {
|
let { config }: {
|
||||||
config: FormConfig;
|
config: FormConfig;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { fs_get_node } from "lib/FilesystemAPI.svelte";
|
|||||||
import { css_from_path } from "filesystem/edit_window/Branding";
|
import { css_from_path } from "filesystem/edit_window/Branding";
|
||||||
import { loading_run, loading_store } from "lib/Loading";
|
import { loading_run, loading_store } from "lib/Loading";
|
||||||
import Spinner from "util/Spinner.svelte";
|
import Spinner from "util/Spinner.svelte";
|
||||||
import { get_user } from "lib/PixeldrainAPI";
|
import { get_user } from "lib/NovaAPI";
|
||||||
import Tree from "./Tree.svelte";
|
import Tree from "./Tree.svelte";
|
||||||
import MenuEntry from "./MenuEntry.svelte";
|
import MenuEntry from "./MenuEntry.svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
@@ -206,6 +206,10 @@ const set_offset = (off: number) => {
|
|||||||
<i class="icon">folder</i>
|
<i class="icon">folder</i>
|
||||||
<span>Filesystem</span>
|
<span>Filesystem</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="button" href="/d/me/.Trash" use:highlight_current_page>
|
||||||
|
<i class="icon">delete</i>
|
||||||
|
<span>Trash</span>
|
||||||
|
</a>
|
||||||
<a class="button" href="/user" use:highlight_current_page>
|
<a class="button" href="/user" use:highlight_current_page>
|
||||||
<i class="icon">person</i>
|
<i class="icon">person</i>
|
||||||
<span>Account</span>
|
<span>Account</span>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SpeedtestPage from "speedtest/SpeedtestPage.svelte";
|
|||||||
import Appearance from "pages/Appearance.svelte";
|
import Appearance from "pages/Appearance.svelte";
|
||||||
import Footer from "layout/Footer.svelte";
|
import Footer from "layout/Footer.svelte";
|
||||||
import { current_page_store, type Tab } from "./RouterStore";
|
import { current_page_store, type Tab } from "./RouterStore";
|
||||||
import { get_user, type User } from "lib/PixeldrainAPI";
|
import { get_user, type User } from "lib/NovaAPI";
|
||||||
import { breadcrumbs_store } from "./BreadcrumbStore";
|
import { breadcrumbs_store } from "./BreadcrumbStore";
|
||||||
|
|
||||||
let pages: Tab[] = [
|
let pages: Tab[] = [
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default defineConfig({
|
|||||||
sourcemap: !production,
|
sourcemap: !production,
|
||||||
lib: {
|
lib: {
|
||||||
entry: "src/wrap.js",
|
entry: "src/wrap.js",
|
||||||
name: "fnx_web",
|
name: "nova_web",
|
||||||
fileName: name,
|
fileName: name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
12
web.go
@@ -9,15 +9,15 @@ import (
|
|||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultConfig is the default configuration for Pixeldrain Web
|
// DefaultConfig is the default configuration for Nova Web
|
||||||
const DefaultConfig = `## Pixeldrain Web UI server configuration
|
const DefaultConfig = `## Nova Web UI server configuration
|
||||||
|
|
||||||
# Address used in the browser for making requests directly to the API. Can be
|
# Address used in the browser for making requests directly to the API. Can be
|
||||||
# relative to the current domain name
|
# relative to the current domain name
|
||||||
api_url_external = "/api"
|
api_url_external = "/api"
|
||||||
|
|
||||||
# Address used to make internal API requests to the backend
|
# Address used to make internal API requests to the backend
|
||||||
api_url_internal = "https://pixeldrain.com/api"
|
api_url_internal = "https://nova.storage/api"
|
||||||
|
|
||||||
# When connecting to the API over a Unix domain socket you should enter the
|
# When connecting to the API over a Unix domain socket you should enter the
|
||||||
# socket path here. api_url_internal needs to be correct too, as the API path
|
# socket path here. api_url_internal needs to be correct too, as the API path
|
||||||
@@ -38,7 +38,7 @@ proxy_api_requests = true
|
|||||||
maintenance_mode = false
|
maintenance_mode = false
|
||||||
`
|
`
|
||||||
|
|
||||||
// Init initializes the Pixeldrain Web UI controllers
|
// Init initializes the Nova Web UI controllers
|
||||||
func Init(r *httprouter.Router, prefix string, setLogLevel bool) {
|
func Init(r *httprouter.Router, prefix string, setLogLevel bool) {
|
||||||
log.Colours = true
|
log.Colours = true
|
||||||
log.Info("Starting web UI server (PID %v)", os.Getpid())
|
log.Info("Starting web UI server (PID %v)", os.Getpid())
|
||||||
@@ -46,8 +46,8 @@ func Init(r *httprouter.Router, prefix string, setLogLevel bool) {
|
|||||||
var conf webcontroller.Config
|
var conf webcontroller.Config
|
||||||
var _, err = config.New(
|
var _, err = config.New(
|
||||||
DefaultConfig,
|
DefaultConfig,
|
||||||
"/etc/fnx",
|
"/etc/nova",
|
||||||
"fnx_web.toml",
|
"nova_web.toml",
|
||||||
&conf,
|
&conf,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func (wc *WebController) serveDirectory(w http.ResponseWriter, r *http.Request,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
td.Title = fmt.Sprintf("%s ~ pixeldrain", node.Path[node.BaseIndex].Name)
|
td.Title = fmt.Sprintf("%s ~ Nova", node.Path[node.BaseIndex].Name)
|
||||||
td.Other = node
|
td.Other = node
|
||||||
td.OGData = wc.metadataFromFilesystem(r, node)
|
td.OGData = wc.metadataFromFilesystem(r, node)
|
||||||
err = wc.templates.Run(w, r, "wrap", td)
|
err = wc.templates.Run(w, r, "wrap", td)
|
||||||
|
|||||||
@@ -82,19 +82,19 @@ func userStyle(style string, hue int) template.CSS {
|
|||||||
def = hackerStyle
|
def = hackerStyle
|
||||||
hue = -1 // Does not support custom hues
|
hue = -1 // Does not support custom hues
|
||||||
case "canta":
|
case "canta":
|
||||||
def = cantaPixeldrainStyle
|
def = cantaNovaStyle
|
||||||
case "skeuos":
|
case "skeuos":
|
||||||
def = skeuosPixeldrainStyle
|
def = skeuosNovaStyle
|
||||||
case "sweet":
|
case "sweet":
|
||||||
def = sweetPixeldrainStyle
|
def = sweetNovaStyle
|
||||||
case "adwaita_dark":
|
case "adwaita_dark":
|
||||||
def = adwaitaDarkStyle
|
def = adwaitaDarkStyle
|
||||||
case "adwaita_light":
|
case "adwaita_light":
|
||||||
def = adwaitaLightStyle
|
def = adwaitaLightStyle
|
||||||
case "pixeldrain98":
|
case "nova98":
|
||||||
def = pixeldrain98Style
|
def = nova98Style
|
||||||
case "pixeldrain98_dark":
|
case "nova98_dark":
|
||||||
def = pixeldrain98StyleDark
|
def = nova98StyleDark
|
||||||
}
|
}
|
||||||
|
|
||||||
if hue >= 0 && hue <= 360 {
|
if hue >= 0 && hue <= 360 {
|
||||||
@@ -370,7 +370,7 @@ var hackerStyle = styleSheet{
|
|||||||
CardColor: HSL{120, .4, .05},
|
CardColor: HSL{120, .4, .05},
|
||||||
}
|
}
|
||||||
|
|
||||||
var cantaPixeldrainStyle = styleSheet{
|
var cantaNovaStyle = styleSheet{
|
||||||
Input: HSL{167, .06, .30}, // hsl(167, 6%, 30%)
|
Input: HSL{167, .06, .30}, // hsl(167, 6%, 30%)
|
||||||
InputHover: HSL{167, .06, .35}, // hsl(167, 6%, 30%)
|
InputHover: HSL{167, .06, .35}, // hsl(167, 6%, 30%)
|
||||||
InputText: HSL{0, 0, 1},
|
InputText: HSL{0, 0, 1},
|
||||||
@@ -385,7 +385,7 @@ var cantaPixeldrainStyle = styleSheet{
|
|||||||
CardColor: HSL{170, .05, .26},
|
CardColor: HSL{170, .05, .26},
|
||||||
}
|
}
|
||||||
|
|
||||||
var skeuosPixeldrainStyle = styleSheet{
|
var skeuosNovaStyle = styleSheet{
|
||||||
Input: HSL{226, .15, .23}, //hsl(226, 15%, 23%)
|
Input: HSL{226, .15, .23}, //hsl(226, 15%, 23%)
|
||||||
InputHover: HSL{226, .15, .28},
|
InputHover: HSL{226, .15, .28},
|
||||||
InputText: HSL{60, .06, .93},
|
InputText: HSL{60, .06, .93},
|
||||||
@@ -452,7 +452,7 @@ var nordLightStyle = styleSheet{
|
|||||||
CardColor: nord5,
|
CardColor: nord5,
|
||||||
}
|
}
|
||||||
|
|
||||||
var sweetPixeldrainStyle = styleSheet{
|
var sweetNovaStyle = styleSheet{
|
||||||
Input: HSL{229, .25, .18}, // hsl(229, 25%, 14%)
|
Input: HSL{229, .25, .18}, // hsl(229, 25%, 14%)
|
||||||
InputHover: HSL{229, .25, .23}, // hsl(229, 25%, 14%)
|
InputHover: HSL{229, .25, .23}, // hsl(229, 25%, 14%)
|
||||||
InputText: HSL{223, .13, .79},
|
InputText: HSL{223, .13, .79},
|
||||||
@@ -610,7 +610,7 @@ hr,
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
var pixeldrain98Style = styleSheet{
|
var nova98Style = styleSheet{
|
||||||
Input: RGB{190, 190, 190},
|
Input: RGB{190, 190, 190},
|
||||||
InputHover: RGB{220, 220, 220},
|
InputHover: RGB{220, 220, 220},
|
||||||
InputText: RGB{0, 0, 0},
|
InputText: RGB{0, 0, 0},
|
||||||
@@ -629,7 +629,7 @@ var pixeldrain98Style = styleSheet{
|
|||||||
StyleOverrides: override98,
|
StyleOverrides: override98,
|
||||||
}
|
}
|
||||||
|
|
||||||
var pixeldrain98StyleDark = styleSheet{
|
var nova98StyleDark = styleSheet{
|
||||||
Input: HSL{0, 0, .25},
|
Input: HSL{0, 0, .25},
|
||||||
InputHover: HSL{0, 0, .30},
|
InputHover: HSL{0, 0, .30},
|
||||||
InputText: RGB{255, 255, 255},
|
InputText: RGB{255, 255, 255},
|
||||||
|
|||||||
54
webcontroller/sveltekit.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package webcontroller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"fornaxian.tech/log"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New initializes a new WebController by registering all the request handlers
|
||||||
|
// and parsing all templates in the resource directory
|
||||||
|
func serveSK(r *httprouter.Router) (err error) {
|
||||||
|
// r.ServeFiles("/_app/*filepath", http.Dir(assetsPath+"/_app"))
|
||||||
|
// r.ServeFiles("/style/*filepath", http.Dir(assetsPath+"/style"))
|
||||||
|
|
||||||
|
readdir, err := os.ReadDir(assetsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range readdir {
|
||||||
|
if entry.Type().IsRegular() {
|
||||||
|
serveFile(r, "/"+entry.Name(), assetsPath+"/"+entry.Name())
|
||||||
|
} else if entry.IsDir() {
|
||||||
|
r.ServeFiles("/"+entry.Name()+"/*filepath", http.Dir(assetsPath+"/"+entry.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Debug("Running fallback handler for %s", r.URL.Path)
|
||||||
|
http.ServeFile(w, r, assetsPath+"/fallback.html")
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetsPath = "/home/wim/Workspace/svelte/fnx_sk/build"
|
||||||
|
|
||||||
|
func serveFile(r *httprouter.Router, path, file string) {
|
||||||
|
var handler = func(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
p httprouter.Params,
|
||||||
|
) {
|
||||||
|
http.ServeFile(w, r, assetsPath+"/"+file)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.GET(path, handler)
|
||||||
|
r.HEAD(path, handler)
|
||||||
|
r.OPTIONS(path, handler)
|
||||||
|
r.POST(path, handler)
|
||||||
|
r.PUT(path, handler)
|
||||||
|
r.PATCH(path, handler)
|
||||||
|
r.DELETE(path, handler)
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
|
|||||||
Value: "",
|
Value: "",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: time.Unix(0, 0),
|
Expires: time.Unix(0, 0),
|
||||||
Domain: ".pixeldrain.com",
|
Domain: ".nova.storage",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ func New(r *httprouter.Router, prefix string, conf Config) (wc *WebController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Info("running wrap handler")
|
|
||||||
wc.serveTemplate("wrap", handlerOpts{})(w, r, nil)
|
wc.serveTemplate("wrap", handlerOpts{})(w, r, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -134,15 +133,6 @@ func New(r *httprouter.Router, prefix string, conf Config) (wc *WebController) {
|
|||||||
{GET, "api" /* */, wc.serveMarkdown("api.md", handlerOpts{})},
|
{GET, "api" /* */, wc.serveMarkdown("api.md", handlerOpts{})},
|
||||||
{GET, "d/*path" /* */, wc.serveDirectory},
|
{GET, "d/*path" /* */, wc.serveDirectory},
|
||||||
{GET, "widgets" /* */, wc.serveTemplate("widgets", handlerOpts{})},
|
{GET, "widgets" /* */, wc.serveTemplate("widgets", handlerOpts{})},
|
||||||
{GET, "about" /* */, wc.serveMarkdown("about.md", handlerOpts{})},
|
|
||||||
{GET, "hosting" /* */, wc.serveMarkdown("hosting.md", handlerOpts{})},
|
|
||||||
{GET, "acknowledgements" /**/, wc.serveMarkdown("acknowledgements.md", handlerOpts{})},
|
|
||||||
{GET, "business" /* */, wc.serveMarkdown("business.md", handlerOpts{})},
|
|
||||||
{GET, "limits" /* */, wc.serveMarkdown("limits.md", handlerOpts{})},
|
|
||||||
{GET, "abuse" /* */, wc.serveMarkdown("abuse.md", handlerOpts{})},
|
|
||||||
{GET, "filesystem" /* */, wc.serveMarkdown("filesystem.md", handlerOpts{})},
|
|
||||||
{GET, "100_gigabit_ethernet", wc.serveMarkdown("100_gigabit_ethernet.md", handlerOpts{NoExec: true})},
|
|
||||||
{GET, "apps" /* */, wc.serveTemplate("apps", handlerOpts{})},
|
|
||||||
{GET, "status" /* */, wc.serveTemplate("status", handlerOpts{})},
|
{GET, "status" /* */, wc.serveTemplate("status", handlerOpts{})},
|
||||||
{GET, "theme.css", wc.themeHandler},
|
{GET, "theme.css", wc.themeHandler},
|
||||||
} {
|
} {
|
||||||
@@ -287,5 +277,5 @@ func (wc *WebController) getAPIKey(r *http.Request) (key string, err error) {
|
|||||||
return cookie.Value, nil
|
return cookie.Value, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", errors.New("not a valid pixeldrain authentication cookie")
|
return "", errors.New("not a valid Nova authentication cookie")
|
||||||
}
|
}
|
||||||
|
|||||||