Compare commits
36 Commits
master
...
feature/us
Author | SHA1 | Date |
---|---|---|
Derek | 89bfd8756c | |
Derek | 02ea66c2b1 | |
Derek | 650421695c | |
Derek | 00482e29f2 | |
Derek | 25ca7bb9bd | |
Derek | 1bb3e59a46 | |
Derek | f2f215acda | |
Derek | 7e507748c4 | |
Derek | ce29702f50 | |
Derek | c549e1c942 | |
Derek | 93d6c235f5 | |
Derek | 4d7a81c0c9 | |
Derek | 12f086c650 | |
Derek | 680e52a6ab | |
Derek | d8a9565dc3 | |
Derek | 8add5cefda | |
Derek | 58bd4895ce | |
Derek | e4b0f4cff8 | |
Derek | c001137094 | |
Derek | 838637de55 | |
Derek | aa0921aae6 | |
Derek | 333dbf9faa | |
Derek | d729ab8ef0 | |
Derek | d68be11789 | |
Derek | b793963d8f | |
Derek | 76509445ff | |
Derek | c7a9639a29 | |
Derek | e90f6fb215 | |
Derek | 3e5cb113a7 | |
Derek | cee989e772 | |
Derek | edbf1bff5c | |
Derek | 83b386390f | |
Derek | 5662757981 | |
Derek | 3a32127ba2 | |
Derek | 6a63b32f9b | |
Derek | 555f4f57e9 |
|
@ -1 +1,4 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.db
|
||||||
|
config.json
|
||||||
|
static/components/
|
||||||
|
|
7
Pipfile
7
Pipfile
|
@ -6,6 +6,13 @@ name = "pypi"
|
||||||
[packages]
|
[packages]
|
||||||
flask = "*"
|
flask = "*"
|
||||||
gunicorn = "*"
|
gunicorn = "*"
|
||||||
|
flask-login = "*"
|
||||||
|
flask-sqlalchemy = "*"
|
||||||
|
bcrypt = "*"
|
||||||
|
flask-migrate = "*"
|
||||||
|
click = "*"
|
||||||
|
flask-limiter = "*"
|
||||||
|
eventlet = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "81cb5d5f0b11719d8d9c5ec9cc683fdcf959c652fda256d5552a82d0f459a99c"
|
"sha256": "2e5352c6c0be1150d27c750324f7c132acf21f9bb4c9dcd04c8370c96761400a"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -16,13 +16,113 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"alembic": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:52d73b1d750f1414fa90c25a08da47b87de1e4ad883935718a8f36396e19e78e",
|
||||||
|
"sha256:eb7db9b4510562ec37c91d00b00d95fde076c1030d3f661aea882eec532b3565"
|
||||||
|
],
|
||||||
|
"version": "==1.0.0"
|
||||||
|
},
|
||||||
|
"bcrypt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01477981abf74e306e8ee31629a940a5e9138de000c6b0898f7f850461c4a0a5",
|
||||||
|
"sha256:054d6e0acaea429e6da3613fcd12d05ee29a531794d96f6ab959f29a39f33391",
|
||||||
|
"sha256:0872eeecdf9a429c1420158500eedb323a132bc5bf3339475151c52414729e70",
|
||||||
|
"sha256:09a3b8c258b815eadb611bad04ca15ec77d86aa9ce56070e1af0d5932f17642a",
|
||||||
|
"sha256:0f317e4ffbdd15c3c0f8ab5fbd86aa9aabc7bea18b5cc5951b456fe39e9f738c",
|
||||||
|
"sha256:2788c32673a2ad0062bea850ab73cffc0dba874db10d7a3682b6f2f280553f20",
|
||||||
|
"sha256:321d4d48be25b8d77594d8324c0585c80ae91ac214f62db9098734e5e7fb280f",
|
||||||
|
"sha256:346d6f84ff0b493dbc90c6b77136df83e81f903f0b95525ee80e5e6d5e4eef84",
|
||||||
|
"sha256:34dd60b90b0f6de94a89e71fcd19913a30e83091c8468d0923a93a0cccbfbbff",
|
||||||
|
"sha256:3b4c23300c4eded8895442c003ae9b14328ae69309ac5867e7530de8bdd7875d",
|
||||||
|
"sha256:43d1960e7db14042319c46925892d5fa99b08ff21d57482e6f5328a1aca03588",
|
||||||
|
"sha256:49e96267cd9be55a349fd74f9852eb9ae2c427cd7f6455d0f1765d7332292832",
|
||||||
|
"sha256:63e06ffdaf4054a89757a3a1ab07f1b922daf911743114a54f7c561b9e1baa58",
|
||||||
|
"sha256:67ed1a374c9155ec0840214ce804616de49c3df9c5bc66740687c1c9b1cd9e8d",
|
||||||
|
"sha256:6b662a5669186439f4f583636c8d6ea77cf92f7cfe6aae8d22edf16c36840574",
|
||||||
|
"sha256:6efd9ca20aefbaf2e7e6817a2c6ed4a50ff6900fafdea1bcb1d0e9471743b144",
|
||||||
|
"sha256:8569844a5d8e1fdde4d7712a05ab2e6061343ac34af6e7e3d7935b2bd1907bfd",
|
||||||
|
"sha256:8629ea6a8a59f865add1d6a87464c3c676e60101b8d16ef404d0a031424a8491",
|
||||||
|
"sha256:988cac675e25133d01a78f2286189c1f01974470817a33eaf4cfee573cfb72a5",
|
||||||
|
"sha256:9a6fedda73aba1568962f7543a1f586051c54febbc74e87769bad6a4b8587c39",
|
||||||
|
"sha256:9eced8962ce3b7124fe20fd358cf8c7470706437fa064b9874f849ad4c5866fc",
|
||||||
|
"sha256:a005ed6163490988711ff732386b08effcbf8df62ae93dd1e5bda0714fad8afb",
|
||||||
|
"sha256:ae35dbcb6b011af6c840893b32399252d81ff57d52c13e12422e16b5fea1d0fb",
|
||||||
|
"sha256:b1e8491c6740f21b37cca77bc64677696a3fb9f32360794d57fa8477b7329eda",
|
||||||
|
"sha256:c906bdb482162e9ef48eea9f8c0d967acceb5c84f2d25574c7d2a58d04861df1",
|
||||||
|
"sha256:cb18ffdc861dbb244f14be32c47ab69604d0aca415bee53485fcea4f8e93d5ef",
|
||||||
|
"sha256:cc2f24dc1c6c88c56248e93f28d439ee4018338567b0bbb490ea26a381a29b1e",
|
||||||
|
"sha256:d860c7fff18d49e20339fc6dffc2d485635e36d4b2cccf58f45db815b64100b4",
|
||||||
|
"sha256:d86da365dda59010ba0d1ac45aa78390f56bf7f992e65f70b3b081d5e5257b09",
|
||||||
|
"sha256:e22f0997622e1ceec834fd25947dc2ee2962c2133ea693d61805bc867abaf7ea",
|
||||||
|
"sha256:f2fe545d27a619a552396533cddf70d83cecd880a611cdfdbb87ca6aec52f66b",
|
||||||
|
"sha256:f425e925485b3be48051f913dbe17e08e8c48588fdf44a26b8b14067041c0da6",
|
||||||
|
"sha256:f7fd3ed3745fe6e81e28dc3b3d76cce31525a91f32a387e1febd6b982caf8cdb",
|
||||||
|
"sha256:f9210820ee4818d84658ed7df16a7f30c9fba7d8b139959950acef91745cc0f7"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.1.4"
|
||||||
|
},
|
||||||
|
"cffi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
|
||||||
|
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
|
||||||
|
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
|
||||||
|
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
|
||||||
|
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
|
||||||
|
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
|
||||||
|
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
|
||||||
|
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
|
||||||
|
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
|
||||||
|
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
|
||||||
|
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
|
||||||
|
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
|
||||||
|
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
|
||||||
|
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
|
||||||
|
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
|
||||||
|
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
|
||||||
|
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
|
||||||
|
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
|
||||||
|
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
|
||||||
|
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
|
||||||
|
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
|
||||||
|
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
|
||||||
|
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
|
||||||
|
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
|
||||||
|
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
|
||||||
|
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
|
||||||
|
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
|
||||||
|
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
|
||||||
|
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
|
||||||
|
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
|
||||||
|
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
|
||||||
|
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
|
||||||
|
],
|
||||||
|
"version": "==1.11.5"
|
||||||
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
|
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
|
||||||
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
|
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==6.7"
|
"version": "==6.7"
|
||||||
},
|
},
|
||||||
|
"dnspython": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c",
|
||||||
|
"sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31"
|
||||||
|
],
|
||||||
|
"version": "==1.15.0"
|
||||||
|
},
|
||||||
|
"eventlet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8",
|
||||||
|
"sha256:d9d31a3c8dbcedbcce5859a919956d934685b17323fc80e1077cb344a2ffa68d"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.24.1"
|
||||||
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||||
|
@ -31,13 +131,67 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.0.2"
|
"version": "==1.0.2"
|
||||||
},
|
},
|
||||||
"gunicorn": {
|
"flask-limiter": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7ef2b828b335ed58e3b64ffa84caceb0a7dd7c5ca12f217241350dec36a1d5dc",
|
"sha256:8cce98dcf25bf2ddbb824c2b503b4fc8e1a139154240fd2c60d9306bad8a0db8"
|
||||||
"sha256:bc59005979efb6d2dd7d5ba72d99f8a8422862ad17ff3a16e900684630dd2a10"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==19.8.1"
|
"version": "==1.0.1"
|
||||||
|
},
|
||||||
|
"flask-login": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.4.1"
|
||||||
|
},
|
||||||
|
"flask-migrate": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:83ebc105f87357ddd3968f83510d2b1092f006660b1c6ba07a4efce036ca567d",
|
||||||
|
"sha256:cd1b4e6cb829eeb41c02ad9202d83bef5f4b7a036dd9fad72ce96ad1e22efb07"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.2.1"
|
||||||
|
},
|
||||||
|
"flask-sqlalchemy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b",
|
||||||
|
"sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.3.2"
|
||||||
|
},
|
||||||
|
"greenlet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0411b5bf0de5ec11060925fd811ad49073fa19f995bcf408839eb619b59bb9f7",
|
||||||
|
"sha256:131f4ed14f0fd28d2a9fa50f79a57d5ed1c8f742d3ccac3d773fee09ef6fe217",
|
||||||
|
"sha256:13510d32f8db72a0b3e1720dbf8cba5c4eecdf07abc4cb631982f51256c453d1",
|
||||||
|
"sha256:31dc4d77ef04ab0460d024786f51466dbbc274fda7c8aad0885a6df5ff8d642e",
|
||||||
|
"sha256:35021d9fecea53b21e4defec0ff3ad69a8e2b75aca1ceddd444a5ba71216547e",
|
||||||
|
"sha256:426a8ef9e3b97c27e841648241c2862442c13c91ec4a48c4a72b262ccf30add9",
|
||||||
|
"sha256:58217698193fb94f3e6ff57eed0ae20381a8d06c2bc10151f76c06bb449a3a19",
|
||||||
|
"sha256:5f45adbbb69281845981bb4e0a4efb8a405f10f3cd6c349cb4a5db3357c6bf93",
|
||||||
|
"sha256:5fdb524767288f7ad161d2182f7ed6cafc0a283363728dcd04b9485f6411547c",
|
||||||
|
"sha256:71fbee1f7ef3fb42efa3761a8faefc796e7e425f528de536cfb4c9de03bde885",
|
||||||
|
"sha256:80bd314157851d06f7db7ca527082dbb0ee97afefb529cdcd59f7a5950927ba0",
|
||||||
|
"sha256:b843c9ef6aed54a2649887f55959da0031595ccfaf7e7a0ba7aa681ffeaa0aa1",
|
||||||
|
"sha256:c6a05ef8125503d2d282ccf1448e3599b8a6bd805c3cdee79760fa3da0ea090e",
|
||||||
|
"sha256:deeda2769a52db840efe5bf7bdf7cefa0ae17b43a844a3259d39fb9465c8b008",
|
||||||
|
"sha256:e66f8b09eec1afdcab947d3a1d65b87b25fde39e9172ae1bec562488335633b4",
|
||||||
|
"sha256:e8db93045414980dbada8908d49dbbc0aa134277da3ff613b3e548cb275bdd37",
|
||||||
|
"sha256:f1cc268a15ade58d9a0c04569fe6613e19b8b0345b64453064e2c3c6d79051af",
|
||||||
|
"sha256:fe3001b6a4f3f3582a865b9e5081cc548b973ec20320f297f5e2d46860e9c703",
|
||||||
|
"sha256:fe85bf7adb26eb47ad53a1bae5d35a28df16b2b93b89042a3a28746617a4738d"
|
||||||
|
],
|
||||||
|
"version": "==0.4.14"
|
||||||
|
},
|
||||||
|
"gunicorn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
|
||||||
|
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==19.9.0"
|
||||||
},
|
},
|
||||||
"itsdangerous": {
|
"itsdangerous": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -52,12 +206,64 @@
|
||||||
],
|
],
|
||||||
"version": "==2.10"
|
"version": "==2.10"
|
||||||
},
|
},
|
||||||
|
"limits": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9df578f4161017d79f5188609f1d65f6b639f8aad2914c3960c9252e56a0ff95",
|
||||||
|
"sha256:a017b8d9e9da6761f4574642149c337f8f540d4edfe573fb91ad2c4001a2bc76"
|
||||||
|
],
|
||||||
|
"version": "==1.3"
|
||||||
|
},
|
||||||
|
"mako": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"
|
||||||
|
],
|
||||||
|
"version": "==1.0.7"
|
||||||
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
||||||
],
|
],
|
||||||
"version": "==1.0"
|
"version": "==1.0"
|
||||||
},
|
},
|
||||||
|
"monotonic": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
|
||||||
|
"sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"
|
||||||
|
],
|
||||||
|
"version": "==1.5"
|
||||||
|
},
|
||||||
|
"pycparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
|
||||||
|
],
|
||||||
|
"version": "==2.18"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
|
||||||
|
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
|
||||||
|
],
|
||||||
|
"version": "==2.7.3"
|
||||||
|
},
|
||||||
|
"python-editor": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a3c066acee22a1c94f63938341d4fb374e3fdd69366ed6603d7b24bed1efc565"
|
||||||
|
],
|
||||||
|
"version": "==1.0.3"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||||
|
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||||
|
],
|
||||||
|
"version": "==1.11.0"
|
||||||
|
},
|
||||||
|
"sqlalchemy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ef6569ad403520ee13e180e1bfd6ed71a0254192a934ec1dbd3dbf48f4aa9524"
|
||||||
|
],
|
||||||
|
"version": "==1.2.11"
|
||||||
|
},
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Share
|
||||||
|
|
||||||
|
because ftp was too easy
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
It's easy peasy my dude
|
||||||
|
|
||||||
|
1. Install pip and pipenv
|
||||||
|
```bash
|
||||||
|
apt install pip3
|
||||||
|
pip3 install pipenv
|
||||||
|
apt install npm
|
||||||
|
npm install bower
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Get dependencies
|
||||||
|
```bash
|
||||||
|
pipenv install
|
||||||
|
bower install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Initialize
|
||||||
|
```bash
|
||||||
|
pipenv run python setup.py serve_dir [--secret SECRET_KEY] [--db CONNECTION_STRING]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run it
|
||||||
|
+ For testing
|
||||||
|
```bash
|
||||||
|
pipenv shell
|
||||||
|
FLASK_APP="notpiracyiswear.py" flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
+ For production
|
||||||
|
```bash
|
||||||
|
pipenv run gunicorn notpiracyiswear:app -k 'eventlet'
|
||||||
|
```
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "skehsucks-share",
|
||||||
|
"authors": [
|
||||||
|
"Derek Schmidt <skehmatics@gmail.com>"
|
||||||
|
],
|
||||||
|
"description": "ftp but worse",
|
||||||
|
"main": "notpiracyiswear.py",
|
||||||
|
"license": "GPL",
|
||||||
|
"homepage": "",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"webcomponentsjs": "^2.0.1",
|
||||||
|
"vaadin-upload": "^4.1.0",
|
||||||
|
"clipboard-copy": "advanced-rest-client/clipboard-copy#^2.0.1",
|
||||||
|
"paper-tooltip": "PolymerElements/paper-tooltip#^2.1.1",
|
||||||
|
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.1.1"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"webcomponentsjs": "^v1.1.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Generic single-database configuration.
|
|
@ -0,0 +1,45 @@
|
||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
|
@ -0,0 +1,88 @@
|
||||||
|
from __future__ import with_statement
|
||||||
|
from alembic import context
|
||||||
|
from sqlalchemy import engine_from_config, pool
|
||||||
|
from logging.config import fileConfig
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
from flask import current_app
|
||||||
|
config.set_main_option('sqlalchemy.url',
|
||||||
|
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||||
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(url=url, compare_type=True)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
engine = engine_from_config(config.get_section(config.config_ini_section),
|
||||||
|
prefix='sqlalchemy.',
|
||||||
|
poolclass=pool.NullPool)
|
||||||
|
|
||||||
|
connection = engine.connect()
|
||||||
|
context.configure(connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
process_revision_directives=process_revision_directives,
|
||||||
|
compare_type=True,
|
||||||
|
**current_app.extensions['migrate'].configure_args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 49984f04bc27
|
||||||
|
Revises: ea5307f715d1
|
||||||
|
Create Date: 2018-09-08 10:28:27.031642
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '49984f04bc27'
|
||||||
|
down_revision = 'ea5307f715d1'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('user', sa.Column('can_manage', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||||
|
op.add_column('user', sa.Column('can_refer', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||||
|
op.add_column('user', sa.Column('can_upload', sa.Boolean(), nullable=False, server_default=sa.false()))
|
||||||
|
user = sa.sql.table('user',
|
||||||
|
sa.sql.column('username', sa.String),
|
||||||
|
sa.sql.column('can_refer', sa.Boolean()),
|
||||||
|
sa.sql.column('can_upload', sa.Boolean()),
|
||||||
|
sa.sql.column('can_manage', sa.Boolean())
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
user.update().\
|
||||||
|
where(user.c.username==op.inline_literal('admin')).\
|
||||||
|
values({'can_refer':op.inline_literal(True), 'can_upload':op.inline_literal(True), 'can_manage':op.inline_literal(True)})
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('user', 'can_upload')
|
||||||
|
op.drop_column('user', 'can_refer')
|
||||||
|
op.drop_column('user', 'can_manage')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,38 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 8c5a3947f711
|
||||||
|
Revises:
|
||||||
|
Create Date: 2018-05-31 09:50:58.314665
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8c5a3947f711'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('user',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=50), nullable=False),
|
||||||
|
sa.Column('password', sa.String(length=64), nullable=False),
|
||||||
|
sa.Column('active', sa.Boolean(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_user_active'), 'user', ['active'], unique=False)
|
||||||
|
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_user_username'), table_name='user')
|
||||||
|
op.drop_index(op.f('ix_user_active'), table_name='user')
|
||||||
|
op.drop_table('user')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,38 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: bb298ef84235
|
||||||
|
Revises: 8c5a3947f711
|
||||||
|
Create Date: 2018-05-31 09:53:34.164700
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'bb298ef84235'
|
||||||
|
down_revision = '8c5a3947f711'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('referal',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('key', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('message', sa.String(), nullable=True),
|
||||||
|
sa.Column('kwargs', sa.PickleType(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_referal_key'), 'referal', ['key'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_referal_key'), table_name='referal')
|
||||||
|
op.drop_table('referal')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: ea5307f715d1
|
||||||
|
Revises: bb298ef84235
|
||||||
|
Create Date: 2018-05-31 15:47:34.645982
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'ea5307f715d1'
|
||||||
|
down_revision = 'bb298ef84235'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('referal', 'key',
|
||||||
|
existing_type=sa.String(length=36),
|
||||||
|
type_=sa.String(length=43),
|
||||||
|
existing_nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('referal', 'key',
|
||||||
|
existing_type=sa.String(length=43),
|
||||||
|
type_=sa.String(length=36),
|
||||||
|
existing_nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,59 @@
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin
|
||||||
|
import bcrypt
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
class User(db.Model, UserMixin):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(50), unique=True, nullable=False, index=True)
|
||||||
|
password = db.Column(db.String(64), nullable=False)
|
||||||
|
|
||||||
|
active = db.Column(db.Boolean(), nullable=False, index=True)
|
||||||
|
can_refer = db.Column(db.Boolean(), nullable=False)
|
||||||
|
can_upload = db.Column(db.Boolean(), nullable=False)
|
||||||
|
can_manage = db.Column(db.Boolean(), nullable=False)
|
||||||
|
|
||||||
|
referals = db.relationship('Referal', backref='user', lazy=True)
|
||||||
|
|
||||||
|
def __init__(self, username, password, active=True, can_refer=False, can_upload=False, can_manage=False):
|
||||||
|
self.username = username
|
||||||
|
self.set_password(password)
|
||||||
|
self.active = active
|
||||||
|
self.can_refer = can_refer
|
||||||
|
self.can_upload = can_upload
|
||||||
|
self.can_manage = can_manage
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<User {username} : active = {is_active}>".format(username=self.username, is_active=self.active)
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
if len(password) > 72:
|
||||||
|
raise AttributeError("Password to hash is longer than 72 characters (bcrypt truncates after 72)")
|
||||||
|
self.password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return bcrypt.checkpw(password.encode(), self.password)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return self.active
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
class Referal(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
key = db.Column(db.String(43), index=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
message = db.Column(db.String(), nullable=True)
|
||||||
|
kwargs = db.Column(db.PickleType, nullable=True)
|
||||||
|
|
||||||
|
def __init__(self, user, message=None, **kwargs):
|
||||||
|
self.user_id = user.id
|
||||||
|
self.key = secrets.token_urlsafe()
|
||||||
|
self.message = message
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Referal {id} : user = {user}>".format(id=self.id, user=self.user.username)
|
|
@ -1,24 +1,197 @@
|
||||||
from flask import Flask, render_template, send_from_directory, redirect, url_for
|
from flask import Flask, render_template, flash, send_from_directory, redirect, request, session, url_for, abort
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from flask_login import LoginManager, current_user, login_user, logout_user, login_required, fresh_login_required
|
||||||
|
from flask_limiter import Limiter
|
||||||
|
import flask_limiter.util
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from models import db, User, Referal
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_pyfile('settings.cfg')
|
app.config.from_json('config.json')
|
||||||
|
|
||||||
|
db.app = app
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
login_manager.login_view = "login"
|
||||||
|
login_manager.login_message = "slap them creds in to continue"
|
||||||
|
login_manager.needs_refresh_message = "no cookies for you - log in properly to continue"
|
||||||
|
login_manager.needs_refresh_message_category = 'info'
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(id):
|
||||||
|
return User.query.get(id)
|
||||||
|
|
||||||
|
limiter = Limiter(app, key_func=flask_limiter.util.get_ipaddr, headers_enabled=True)
|
||||||
|
|
||||||
|
def is_secure_path(path, servepath=None):
|
||||||
|
realpath = os.path.abspath(path) + os.path.sep
|
||||||
|
if servepath is None:
|
||||||
|
servepath = os.path.abspath(app.config['SERVE_DIR'])
|
||||||
|
else:
|
||||||
|
servepath = os.path.abspath(servepath)
|
||||||
|
|
||||||
|
return realpath.startswith(servepath)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
# TODO: Login / user management here
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('browse'))
|
return redirect(url_for('browse'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
@limiter.limit("8/minute;1/second", exempt_when=lambda : request.method == 'GET')
|
||||||
|
def login():
|
||||||
|
if request.method == 'GET':
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(request.args.get('next') or url_for('index'))
|
||||||
|
else:
|
||||||
|
return render_template('login.html')
|
||||||
|
else:
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if user is None:
|
||||||
|
flash("username / password all fucked up", 'error')
|
||||||
|
return redirect(request.script_root + request.path)
|
||||||
|
|
||||||
|
if user.check_password(password) == True:
|
||||||
|
success = login_user(user)
|
||||||
|
if success:
|
||||||
|
flash("holy cow you are logged in!", 'info')
|
||||||
|
return redirect(request.args.get('next') or url_for('index'))
|
||||||
|
else:
|
||||||
|
flash("oof ouch u banned my dude", 'error')
|
||||||
|
return redirect(request.script_root + request.path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
flash("username / password all fucked up", 'error')
|
||||||
|
return redirect(request.script_root + request.path)
|
||||||
|
|
||||||
|
@app.route('/signup', methods=['GET', 'POST'])
|
||||||
|
@limiter.limit("5/minute;1/second", exempt_when=lambda : request.method == 'GET')
|
||||||
|
def signup():
|
||||||
|
referal_key = request.args.get('referalkey')
|
||||||
|
if not referal_key:
|
||||||
|
return render_template('genericerror.html', message="I don't trust y'all. Signups are by referal only rn.")
|
||||||
|
|
||||||
|
referal = Referal.query.filter_by(key=referal_key).first()
|
||||||
|
if not referal:
|
||||||
|
return render_template('genericerror.html', message="Someone gave you a bad referal code. What a dumbass. \nGo ask for a new one.")
|
||||||
|
if not referal.user.active == True:
|
||||||
|
return render_template('genericerror.html', message="Looks like {user} got a good 'ol banaroni. Referals from them aren't valid anymore. \nSorry pal.".format(user=referal.user.username))
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('signup.html', referer_username=referal.user.username, referal_message=referal.message or "oh worm? make an account my dude")
|
||||||
|
else:
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
referal_kwargs = referal.kwargs or {}
|
||||||
|
new_user = User(username, password, **referal_kwargs)
|
||||||
|
|
||||||
|
db.session.add(new_user)
|
||||||
|
db.session.delete(referal)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
login_success = login_user(new_user)
|
||||||
|
if login_success:
|
||||||
|
return redirect(url_for('browse'))
|
||||||
|
else:
|
||||||
|
flash("couldn't log you in because ?? guess you'll have to do it yourself", 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
flash("bye binch", 'info')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/upload/', methods=['GET', 'POST'])
|
||||||
|
@app.route('/upload/<path:path>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def upload(path=None):
|
||||||
|
if not current_user.can_upload:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
path = path if path is not None else ''
|
||||||
|
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(app.config['SERVE_DIR'], path)):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('upload.html')
|
||||||
|
else:
|
||||||
|
if 'file' in request.files and secure_filename(request.files['file'].filename) != '':
|
||||||
|
file = request.files['file']
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
fullpath = os.path.join(app.config['SERVE_DIR'], path, filename)
|
||||||
|
if os.path.exists(fullpath):
|
||||||
|
return json.dumps({'status': 'error', 'message': "Filename already exists"}), 409
|
||||||
|
file.save(fullpath)
|
||||||
|
relpath = os.path.join(path, filename)
|
||||||
|
return json.dumps({'status': 'success', 'link': url_for('files', path = relpath, _external = True)}), 201
|
||||||
|
else:
|
||||||
|
return json.dumps({'status': 'error', 'message': "No valid file attached to this request"}), 400
|
||||||
|
|
||||||
|
@app.route('/refer', methods=['GET', 'POST'])
|
||||||
|
@fresh_login_required
|
||||||
|
@limiter.limit("50/hour;2/second", key_func=lambda : current_user, exempt_when=lambda : request.method == 'GET')
|
||||||
|
def refer():
|
||||||
|
if not current_user.can_refer:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('new_referal.html')
|
||||||
|
else:
|
||||||
|
can_refer = request.form.get('refer', 'off') == 'on'
|
||||||
|
can_upload = request.form.get('upload', 'off') == 'on'
|
||||||
|
can_manage = request.form.get('manage', 'off') == 'on'
|
||||||
|
|
||||||
|
if (not current_user.can_refer and can_refer) or (not current_user.can_upload and can_upload) or (not current_user.can_manage and can_manage):
|
||||||
|
flash('You cant just grant permissions you yourself dont have, dingo', 'error')
|
||||||
|
return redirect(url_for('refer'))
|
||||||
|
|
||||||
|
message = request.form.get('message', None)
|
||||||
|
referal = Referal(current_user, message=message, can_refer=can_refer, can_upload=can_upload, can_manage=can_manage)
|
||||||
|
db.session.add(referal)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return redirect(url_for('show_referal', key=referal.key))
|
||||||
|
|
||||||
|
@app.route('/refer/show', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def show_referal():
|
||||||
|
if not current_user.can_refer:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
return render_template('show_referal.html', key=request.args.get('key'))
|
||||||
|
|
||||||
@app.route('/browse/')
|
@app.route('/browse/')
|
||||||
@app.route('/browse/<path:path>')
|
@app.route('/browse/<path:path>')
|
||||||
|
@login_required
|
||||||
def browse(path=None):
|
def browse(path=None):
|
||||||
# Search for files in path
|
searchpath = os.path.join(app.config['SERVE_DIR'], path) if path is not None else app.config['SERVE_DIR']
|
||||||
searchpath = os.path.join('files', path) if path is not None else 'files'
|
|
||||||
|
if not is_secure_path(searchpath):
|
||||||
|
abort(400)
|
||||||
|
|
||||||
# 404 if no such path exists
|
# 404 if no such path exists
|
||||||
if not os.path.exists(searchpath):
|
if not os.path.exists(searchpath):
|
||||||
return render_template('404.html'), 404
|
abort(404)
|
||||||
|
|
||||||
if not os.path.isdir(searchpath):
|
if not os.path.isdir(searchpath):
|
||||||
return redirect(url_for('files', path=path), code=301)
|
return redirect(url_for('files', path=path), code=301)
|
||||||
|
@ -37,9 +210,35 @@ def browse(path = None):
|
||||||
# Render out the item browser
|
# Render out the item browser
|
||||||
return render_template('items.html', files = file_size_isdir_tuple_list, path = os.path.normpath(path) + '/' if path is not None else '')
|
return render_template('items.html', files = file_size_isdir_tuple_list, path = os.path.normpath(path) + '/' if path is not None else '')
|
||||||
|
|
||||||
|
# Expecting lots of traffic? Do these via nginx before you kill your server
|
||||||
@app.route('/files/<path:path>')
|
@app.route('/files/<path:path>')
|
||||||
|
@login_required
|
||||||
def files(path):
|
def files(path):
|
||||||
return send_from_directory('files', path)
|
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
|
||||||
|
abort(400)
|
||||||
|
return send_from_directory(app.config['SERVE_DIR'], path)
|
||||||
|
|
||||||
|
@app.route('/components/<path:path>')
|
||||||
|
def components(path):
|
||||||
|
if not is_secure_path(os.path.join('static', 'components', path), servepath=os.path.join('static', 'components')):
|
||||||
|
abort(400)
|
||||||
|
return send_from_directory(os.path.join('static', 'components'), path)
|
||||||
|
|
||||||
|
@app.route('/custom-components/<path:path>')
|
||||||
|
def custom_components(path):
|
||||||
|
if not is_secure_path(os.path.join('static', 'custom-components', path), servepath=os.path.join('static', 'custom-components')):
|
||||||
|
abort(400)
|
||||||
|
return send_from_directory(os.path.join('static', 'custom-components'), path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boring stuff
|
||||||
|
@app.errorhandler(400)
|
||||||
|
def bad_request(e):
|
||||||
|
return render_template('genericerror.html', message="The request you sent didn't check out."), 400
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def forbidden(e):
|
||||||
|
return render_template('403.html'), 403
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
|
@ -49,5 +248,9 @@ def page_not_found(e):
|
||||||
def internal_error(e):
|
def internal_error(e):
|
||||||
return render_template('500.html'), 500
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
@app.errorhandler(429)
|
||||||
|
def rate_limit(e):
|
||||||
|
return render_template('429.html', back=request.script_root + request.path), 429
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run()
|
app.run()
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
DEBUG = False
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
from models import User
|
||||||
|
from alembic import command
|
||||||
|
import json
|
||||||
|
import secrets
|
||||||
|
import click
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("serve_dir")
|
||||||
|
@click.option('--secret', help="Secret key to use. A random hex string will be generated if not provided")
|
||||||
|
@click.option('--db', 'connection', help="Database connection string. Defaults to \"sqlite:///test.db\"")
|
||||||
|
def main(serve_dir, secret=None, connection=None):
|
||||||
|
config = {"SERVE_DIR": serve_dir, "SECRET_KEY": secret or secrets.token_hex(), "SQLALCHEMY_DATABASE_URI": connection or 'sqlite:///test.db'}
|
||||||
|
with open('config.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
# HACK: this is probably not the best way to make sure the built config is applied before we do anything else
|
||||||
|
from notpiracyiswear import app, db, migrate
|
||||||
|
|
||||||
|
db.create_all()
|
||||||
|
admin = User('admin', app.config['SECRET_KEY'], can_refer=True, can_upload=True)
|
||||||
|
db.session.add(admin)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
config = migrate.get_config()
|
||||||
|
with app.app_context():
|
||||||
|
command.stamp(config, 'head', sql=False, tag=None)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,352 @@
|
||||||
|
<!--
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 Vaadin Ltd.
|
||||||
|
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<link rel="import" href="../components/polymer/polymer-element.html">
|
||||||
|
<link rel="import" href="../components/vaadin-themable-mixin/vaadin-themable-mixin.html">
|
||||||
|
<link rel="import" href="../components/vaadin-progress-bar/src/vaadin-progress-bar.html">
|
||||||
|
<link rel="import" href="../components/vaadin-upload/src/vaadin-upload-icons.html">
|
||||||
|
<link rel="import" href="../components/clipboard-copy/clipboard-copy.html">
|
||||||
|
<link rel="import" href="../components/paper-tooltip/paper-tooltip.html">
|
||||||
|
<!-- chrome is pretty serious about its scopes, so we have to get this twice. -->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
|
||||||
|
<dom-module id="vaadin-permalinked-upload-file">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style include="lumo-field-button">
|
||||||
|
:host {
|
||||||
|
padding: var(--lumo-space-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(:not(:first-child)) {
|
||||||
|
border-top: 1px solid var(--lumo-contrast-10pct);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="row"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="status"],
|
||||||
|
[part="error"] {
|
||||||
|
color: var(--lumo-secondary-text-color);
|
||||||
|
font-size: var(--lumo-font-size-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="info"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="meta"] {
|
||||||
|
width: 0.001px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="name"] {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="commands"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="done-icon"],
|
||||||
|
[part="warning-icon"] {
|
||||||
|
margin-right: var(--lumo-space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When both icons are hidden, let us keep space for one */
|
||||||
|
[part="done-icon"][hidden] + [part="warning-icon"][hidden] {
|
||||||
|
display: block !important;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="done-icon"],
|
||||||
|
[part="warning-icon"] {
|
||||||
|
font-size: var(--lumo-icon-size-m);
|
||||||
|
font-family: 'lumo-icons';
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="start-button"],
|
||||||
|
[part="retry-button"],
|
||||||
|
[part="clear-button"],
|
||||||
|
[part="copy-button"] {
|
||||||
|
flex: none;
|
||||||
|
margin-left: var(--lumo-space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="done-icon"]::before,
|
||||||
|
[part="warning-icon"]::before,
|
||||||
|
[part="start-button"]::before,
|
||||||
|
[part="retry-button"]::before,
|
||||||
|
[part="clear-button"]::before,
|
||||||
|
[part="copy-button"] i {
|
||||||
|
vertical-align: -.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="done-icon"]::before {
|
||||||
|
content: var(--lumo-icons-checkmark);
|
||||||
|
color: var(--lumo-primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="warning-icon"]::before {
|
||||||
|
content: var(--lumo-icons-error);
|
||||||
|
color: var(--lumo-error-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="start-button"]::before {
|
||||||
|
content: var(--lumo-icons-play);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="retry-button"]::before {
|
||||||
|
content: var(--lumo-icons-reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="clear-button"]::before {
|
||||||
|
content: var(--lumo-icons-cross);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="error"] {
|
||||||
|
color: var(--lumo-error-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="progress"] {
|
||||||
|
width: auto;
|
||||||
|
margin-left: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||||
|
margin-right: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="progress"][complete],
|
||||||
|
[part="progress"][error] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div part="row">
|
||||||
|
<div part="info">
|
||||||
|
<div part="done-icon" hidden$="[[!file.complete]]"></div>
|
||||||
|
<div part="warning-icon" hidden$="[[!file.error]]"></div>
|
||||||
|
<slot name="custom-icon"></slot>
|
||||||
|
|
||||||
|
<div part="meta">
|
||||||
|
<div part="name" id="name">[[file.name]]</div>
|
||||||
|
<div part="status" hidden$="[[!file.url]]" id="status">Avalible at <a href="[[file.url]]">[[file.url]]</a></div>
|
||||||
|
<div part="error" id="error" hidden$="[[!file.error]]">[[_errorOrMessage(file.error, file.message)]]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div part="commands">
|
||||||
|
<div part="start-button" file-event="file-start" on-click="_fireFileEvent" hidden$="[[!file.held]]"></div>
|
||||||
|
<div part="retry-button" file-event="file-retry" on-click="_fireFileEvent" hidden$="[[!file.error]]"></div>
|
||||||
|
<div part="clear-button" file-event="file-abort" on-click="_fireFileEvent" hidden$="[[file.complete]]"></div>
|
||||||
|
<div part="copy-button" on-click="_copyUrl" hidden$="[[!file.complete]]">
|
||||||
|
<clipboard-copy id="clipboardomatic" content="[[file.url]]"></clipboard-copy>
|
||||||
|
<i class="material-icons">file_copy</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<paper-tooltip id="copied_notif" for="copy-button" position="right">Copied to clipboard</paper-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<vaadin-progress-bar
|
||||||
|
part="progress"
|
||||||
|
id="progress"
|
||||||
|
value$="[[_formatProgressValue(file.progress)]]"
|
||||||
|
error$="[[file.error]]"
|
||||||
|
indeterminate$="[[file.indeterminate]]"
|
||||||
|
uploading$="[[file.uploading]]"
|
||||||
|
complete$="[[file.complete]]"
|
||||||
|
hidden$="[[file.complete]]">
|
||||||
|
</vaadin-progress-bar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `<vaadin-permalinked-upload-file>` element represents a file in the file list of `<vaadin-upload>`.
|
||||||
|
*
|
||||||
|
* ### Styling
|
||||||
|
*
|
||||||
|
* The following shadow DOM parts are available for styling:
|
||||||
|
*
|
||||||
|
* Part name | Description
|
||||||
|
* ---|---
|
||||||
|
* `row` | File container
|
||||||
|
* `info` | Container for file status icon, file name, status and error messages
|
||||||
|
* `done-icon` | File done status icon
|
||||||
|
* `warning-icon` | File warning status icon
|
||||||
|
* `meta` | Container for file name, status and error messages
|
||||||
|
* `name` | File name
|
||||||
|
* `error` | Error message, shown when error happens
|
||||||
|
* `status` | Status message
|
||||||
|
* `commands` | Container for file command icons
|
||||||
|
* `start-button` | Start file upload button
|
||||||
|
* `retry-button` | Retry file upload button
|
||||||
|
* `clear-button` | Clear file button
|
||||||
|
* `progress`| Progress bar
|
||||||
|
*
|
||||||
|
* The following state attributes are available for styling:
|
||||||
|
*
|
||||||
|
* Attribute | Description | Part name
|
||||||
|
* ---|---|---
|
||||||
|
* `error` | An error has happened during uploading | `:host`
|
||||||
|
* `indeterminate` | Uploading is in progress, but the progress value is unknown | `:host`
|
||||||
|
* `uploading` | Uploading is in progress | `:host`
|
||||||
|
* `complete` | Uploading has finished successfully | `:host`
|
||||||
|
*
|
||||||
|
* See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki)
|
||||||
|
*
|
||||||
|
* @memberof Vaadin
|
||||||
|
* @mixes Vaadin.ThemableMixin
|
||||||
|
* @demo demo/index.html
|
||||||
|
*/
|
||||||
|
class PermalinkedUploadFileElement extends Vaadin.ThemableMixin(Polymer.Element) {
|
||||||
|
static get is() {
|
||||||
|
return 'vaadin-permalinked-upload-file';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
file: Object
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observers() {
|
||||||
|
return [
|
||||||
|
'_fileAborted(file.abort)',
|
||||||
|
'_toggleHostAttribute(file.error, "error")',
|
||||||
|
'_toggleHostAttribute(file.indeterminate, "indeterminate")',
|
||||||
|
'_toggleHostAttribute(file.uploading, "uploading")',
|
||||||
|
'_toggleHostAttribute(file.complete, "complete")',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
_copyUrl(event) {
|
||||||
|
var success = this.$.clipboardomatic.copy();
|
||||||
|
if (success) {
|
||||||
|
var notif = this.$.copied_notif;
|
||||||
|
notif.show();
|
||||||
|
window.setTimeout(function() {
|
||||||
|
notif.hide();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_errorOrMessage(error, message) {
|
||||||
|
if (message) {
|
||||||
|
return message;
|
||||||
|
} else {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fileAborted(abort) {
|
||||||
|
if (abort) {
|
||||||
|
this._remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_remove() {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('file-remove', {
|
||||||
|
detail: {file: this.file},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatProgressValue(progress) {
|
||||||
|
return progress / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
_fireFileEvent(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
return this.dispatchEvent(
|
||||||
|
new CustomEvent(e.target.getAttribute('file-event'), {
|
||||||
|
detail: {file: this.file},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggleHostAttribute(value, attributeName) {
|
||||||
|
const shouldHave = Boolean(value);
|
||||||
|
const has = this.hasAttribute(attributeName);
|
||||||
|
if (has !== shouldHave) {
|
||||||
|
if (shouldHave) {
|
||||||
|
this.setAttribute(attributeName, '');
|
||||||
|
} else {
|
||||||
|
this.removeAttribute(attributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when the retry button is pressed. It is listened by `vaadin-upload`
|
||||||
|
* which will start a new upload process of this file.
|
||||||
|
*
|
||||||
|
* @event file-retry
|
||||||
|
* @param {Object} detail
|
||||||
|
* @param {Object} detail.file file to retry upload of
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when the start button is pressed. It is listened by `vaadin-upload`
|
||||||
|
* which will start a new upload process of this file.
|
||||||
|
*
|
||||||
|
* @event file-start
|
||||||
|
* @param {Object} detail
|
||||||
|
* @param {Object} detail.file file to start upload of
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when abort button is pressed. It is listened by `vaadin-upload` which
|
||||||
|
* will abort the upload in progress, but will not remove the file from the list
|
||||||
|
* to allow the animation to hide the element to be run.
|
||||||
|
*
|
||||||
|
* @event file-abort
|
||||||
|
* @param {Object} detail
|
||||||
|
* @param {Object} detail.file file to abort upload of
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired after the animation to hide the element has finished. It is listened
|
||||||
|
* by `vaadin-upload` which will actually remove the file from the upload
|
||||||
|
* file list.
|
||||||
|
*
|
||||||
|
* @event file-remove
|
||||||
|
* @param {Object} detail
|
||||||
|
* @param {Object} detail.file file to remove from the upload of
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define(PermalinkedUploadFileElement.is, PermalinkedUploadFileElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace Vaadin
|
||||||
|
*/
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
Vaadin.PermalinkedUploadFileElement = PermalinkedUploadFileElement;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</dom-module>
|
|
@ -1,4 +0,0 @@
|
||||||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 209 B |
|
@ -1,4 +0,0 @@
|
||||||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>
|
|
||||||
<path d="M0-.25h24v24H0z" fill="none"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 214 B |
227
static/style.css
227
static/style.css
|
@ -4,40 +4,44 @@ body {
|
||||||
--trinary-background-color: #B0BEC5;
|
--trinary-background-color: #B0BEC5;
|
||||||
--foreground-color: #000000;
|
--foreground-color: #000000;
|
||||||
--secondary-foreground-color: #424242;
|
--secondary-foreground-color: #424242;
|
||||||
|
--foreground-disabled-color: #9e9e9e;
|
||||||
|
|
||||||
|
--info-color: #90caf9;
|
||||||
|
--error-color: #f06292;
|
||||||
|
--error-color-light: #f48fb1;
|
||||||
|
|
||||||
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
color: var(--foreground-color);
|
color: var(--foreground-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
p, b, i {
|
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mainwrapper {
|
#mainwrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
flex-grow: 3;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
#contentarea {
|
#contentarea {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
min-width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex: auto;
|
flex: auto;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#contentarea > *:not(:last-child) {
|
#contentarea > *:not(:last-child) {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
|
@ -51,6 +55,116 @@ p, b, i {
|
||||||
color: var(--secondary-foreground-color);
|
color: var(--secondary-foreground-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login_form {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type=file]) {
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
border-style: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file] {
|
||||||
|
height: 40px;
|
||||||
|
/* background-color: var(--secondary-background-color); */
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: var(--trinary-background-color);
|
||||||
|
border-width: 2px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button {
|
||||||
|
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.28s;
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
color: inherit;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.05s;
|
||||||
|
background-color: var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#login_form > button {
|
||||||
|
height: 38px;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login_form > *:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom_message_text {
|
||||||
|
margin: 12px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > button {
|
||||||
|
margin-top: 32px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[disabled] + .checkboxtext {
|
||||||
|
color: var(--foreground-disabled-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#link_copy {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: var(--secondary-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade { from { opacity: 1; } to { opacity: 0; } }
|
||||||
|
@keyframes slide { from { bottom: 0px; } to { bottom: -100%; } }
|
||||||
|
|
||||||
|
#flash_wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
animation: slide cubic-bezier(0.4, 0, 0.2, 1) 1, fade 1;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-delay: 4s, 4.5s;
|
||||||
|
animation-duration: 1s, 0.5s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 32px;
|
||||||
|
}
|
||||||
|
.flash > p {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.flash {
|
||||||
|
background-color: var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { background-color: var(--error-color); }
|
||||||
|
20% { background-color: var(--error-color-light); }
|
||||||
|
100% { background-color: var(--error-color); }
|
||||||
|
}
|
||||||
|
.flash.error {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
animation: pulse ease 2;
|
||||||
|
animation-fill-mode: backwards;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
.itemwrapper {
|
.itemwrapper {
|
||||||
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
background-color: var(--secondary-background-color);
|
background-color: var(--secondary-background-color);
|
||||||
|
@ -65,14 +179,53 @@ p, b, i {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-name {
|
.item-name {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
min-width: 1%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
.item-size {
|
.item-size {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.item-icon {
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
margin: 16px;
|
||||||
|
padding: 0px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
#headerbar > .iconbutton {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
#headerbar-text {
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0px;
|
||||||
|
margin-right: auto;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.iconbutton {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.iconbutton > img {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
.iconbutton > i {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
a.nostyle:link {
|
a.nostyle:link {
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
|
@ -82,27 +235,59 @@ a.nostyle:visited {
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
#waifuwrapper {
|
button.nostyle {
|
||||||
height: 100%;
|
border: none;
|
||||||
display: flex;
|
background: transparent;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 780px) {
|
|
||||||
#waifu {
|
#waifu-spacesaver {
|
||||||
display: none;
|
width: 100%;
|
||||||
}
|
|
||||||
#waifu-lazyflexhack {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#waifu-lazyflexhack {
|
|
||||||
max-width: 30vw;
|
max-width: 30vw;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
#waifu {
|
#waifu {
|
||||||
|
/* visibility: hidden; */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
|
z-index: -1;
|
||||||
max-width: 30vw;
|
max-width: 30vw;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 780px) {
|
||||||
|
body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#headerbar {
|
||||||
|
background-color: var(--info-color);
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
position: sticky;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
#headerbar > .iconbutton {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
#headerbar-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.iconbutton > img {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.iconbutton > i {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#waifu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#waifu-spacesaver {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'error.html' %}
|
||||||
|
{% block head %}
|
||||||
|
Get out
|
||||||
|
{% endblock %}
|
||||||
|
{% block disc %}
|
||||||
|
You're not welcome here >:c
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'error.html' %}
|
||||||
|
{% block head %}
|
||||||
|
sTOP IT
|
||||||
|
{% endblock %}
|
||||||
|
{% block disc %}
|
||||||
|
Whoa buddy calm down, you're going too fast. Go drink some tea and then <a href="{{ back }}">try again.</a>
|
||||||
|
{% endblock %}
|
|
@ -1,39 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
|
|
||||||
|
|
||||||
<title>it's not piracy i swear</title>
|
|
||||||
<meta name="description" content="ftp but worse">
|
|
||||||
|
|
||||||
<link href="/static/style.css" type="text/css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="mainwrapper">
|
|
||||||
<div id="contentarea">
|
|
||||||
{% for file, size in files %}
|
|
||||||
<a class="nostyle itemwrapper" href="/files/{{ file }}" download>
|
|
||||||
<div class="item">
|
|
||||||
<p class="item-name">{{ file }}</p>
|
|
||||||
<p class="item-size">{{ size }}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<p id="nofileshead">
|
|
||||||
It's fucking nothing!
|
|
||||||
</p>
|
|
||||||
<p id="nofilesdisc">
|
|
||||||
No files avalible yet. Come back later, eh?
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="waifuwrapper">
|
|
||||||
<img id="waifu-lazyflexhack" src="/static/obligitorywaifu.png"></img>
|
|
||||||
<!-- <div id="waifu-spacesaver"></div> -->
|
|
||||||
</div>
|
|
||||||
<img id="waifu" src="/static/obligitorywaifu.png"></img>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<p class="item-name">{{ file }}</p>
|
<p class="item-name">{{ file }}</p>
|
||||||
{% if isdir %}
|
{% if isdir %}
|
||||||
<img src="{{ url_for('static', filename='ic_chevron_right_black.svg') }}" />
|
<i class="item-icon material-icons">chevron_right</i>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="item-size">{{ size|filesizeformat }}</p>
|
<p class="item-size">{{ size|filesizeformat }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<p class="item-name">
|
<p class="item-name">
|
||||||
Back
|
Back
|
||||||
</p>
|
</p>
|
||||||
<img src="{{ url_for('static', filename='ic_chevron_left_black.svg') }}" />
|
<i class="item-icon material-icons">chevron_left</i>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'skel.html' %}
|
||||||
|
{% block body %}
|
||||||
|
<div id="mainwrapper">
|
||||||
|
<div id="contentarea">
|
||||||
|
<form id="login_form" action="" method="post">
|
||||||
|
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
|
||||||
|
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
|
||||||
|
<button type="submit">Sign in</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -6,8 +6,6 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="waifuwrapper">
|
<div id="waifu-spacesaver"></div>
|
||||||
<img id="waifu-lazyflexhack" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
|
|
||||||
</div>
|
|
||||||
<img id="waifu" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
|
<img id="waifu" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends 'skel.html' %}
|
||||||
|
{% block body %}
|
||||||
|
<div id="mainwrapper">
|
||||||
|
<div id="contentarea">
|
||||||
|
<p id="infohead">
|
||||||
|
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
|
||||||
|
</p>
|
||||||
|
<p id="infodisc">
|
||||||
|
Referal links can be used only once to register a new user on this site, giving access to all it's content.
|
||||||
|
Give out with caution.
|
||||||
|
</p>
|
||||||
|
<form id="referal_form" action="" method="post">
|
||||||
|
<div id="refer_container">
|
||||||
|
<input id="refer_permission_checkbox" name="refer" type="checkbox"></input>
|
||||||
|
<p class="checkboxtext">Allow refering other users</p>
|
||||||
|
</div>
|
||||||
|
<div id="upload_container">
|
||||||
|
<input id="upload_permission_checkbox" name="upload" type="checkbox" {{ "disabled" if not current_user.can_upload }}></input>
|
||||||
|
<p class="checkboxtext">Allow uploading new files</p>
|
||||||
|
</div>
|
||||||
|
<div id="admin_container">
|
||||||
|
<input id="admin_permission_checkbox" name="manage" type="checkbox" {{ "disabled" if not current_user.can_manage }}></input>
|
||||||
|
<p class="checkboxtext">Allow administrative actions</p>
|
||||||
|
</div>
|
||||||
|
<input id="custom_message_text" name="message" placeholder="Custom message shown on signup page"></input>
|
||||||
|
<button type="submit">Generate referal link</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends 'skel.html' %}
|
||||||
|
{% block body %}
|
||||||
|
<div id="mainwrapper">
|
||||||
|
<div id="contentarea">
|
||||||
|
<p id="infohead">
|
||||||
|
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
|
||||||
|
</p>
|
||||||
|
<p id="infodisc">
|
||||||
|
Here's your shiny new referal link:
|
||||||
|
</p>
|
||||||
|
<input id="link_copy" readonly value="{{ url_for('signup', referalkey=key, _external=True) }}"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelector('#link_copy').onclick = function(event) {
|
||||||
|
var sel, range;
|
||||||
|
sel = window.getSelection();
|
||||||
|
if(sel.toString() == '') {
|
||||||
|
event.target.select();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
||||||
|
{% extends 'skel.html' %}
|
||||||
|
{% block body %}
|
||||||
|
<div id="mainwrapper">
|
||||||
|
<div id="contentarea">
|
||||||
|
<p id="infohead">
|
||||||
|
{{ referal_message }}
|
||||||
|
</p>
|
||||||
|
<p id="infodisc">
|
||||||
|
- {{ referer_username }}
|
||||||
|
</p>
|
||||||
|
<form id="login_form" action="" method="post" style="padding-top: 32px;">
|
||||||
|
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
|
||||||
|
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
|
||||||
|
<input id="password_confirm_input" name="password" type="password" placeholder="Confirm password" required></input>
|
||||||
|
<button type="submit">Sign up</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var password = document.getElementById("password_input"), confirm_password = document.getElementById("password_confirm_input");
|
||||||
|
|
||||||
|
function validatePassword(){
|
||||||
|
if(password.value != confirm_password.value) {
|
||||||
|
confirm_password.setCustomValidity("Passwords don't match");
|
||||||
|
} else {
|
||||||
|
confirm_password.setCustomValidity('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password.onchange = validatePassword;
|
||||||
|
confirm_password.onkeyup = validatePassword;
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -7,13 +7,46 @@
|
||||||
<title>it's not piracy i swear</title>
|
<title>it's not piracy i swear</title>
|
||||||
<meta name="description" content="ftp but worse">
|
<meta name="description" content="ftp but worse">
|
||||||
|
|
||||||
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||||
|
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
|
||||||
|
{% block imports %}
|
||||||
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<div id="headerbar">
|
||||||
|
<a class="nostyle iconbutton" href="{{ url_for('logout') }}" title="Logout">
|
||||||
|
<i class="material-icons">exit_to_app</i>
|
||||||
|
</a>
|
||||||
|
{% if current_user.can_refer %}
|
||||||
|
<a class="nostyle iconbutton" href="{{ url_for('refer') }}" title="Add new user">
|
||||||
|
<i class="material-icons">person_add</i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.can_upload and path is defined %}
|
||||||
|
<a class="nostyle iconbutton" href="{{ url_for('upload', path=path) }}" title="Upload to current directory">
|
||||||
|
<i class="material-icons">cloud_upload</i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<p id="headerbar-text">it's not piracy, it's digiorno</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<div id="flash_wrapper">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="flash {{ category }}">
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
{% extends 'skel.html' %}
|
||||||
|
{% block imports %}
|
||||||
|
<script src="{{ url_for('components', path='webcomponentsjs/webcomponents-loader.js') }}"></script>
|
||||||
|
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-bind.html') }}">
|
||||||
|
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-repeat.html') }}">
|
||||||
|
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-if.html') }}">
|
||||||
|
<link rel="import" href="{{ url_for('components', path='vaadin-upload/vaadin-upload.html') }}">
|
||||||
|
<link rel="import" href="{{ url_for('custom_components', path='vaadin-permalinked-upload-file.html') }}">
|
||||||
|
<link rel="import" href="{{ url_for('components', path='paper-toggle-button/paper-toggle-button.html') }}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<div id="mainwrapper">
|
||||||
|
<div id="contentarea">
|
||||||
|
<p id="infohead">
|
||||||
|
Files for the file gods
|
||||||
|
</p>
|
||||||
|
<p id="infodisc">
|
||||||
|
Get your upload on my dude.
|
||||||
|
</p>
|
||||||
|
<form class="noscript" action="" method="POST" enctype="multipart/form-data">
|
||||||
|
<input id="upload_input" name="files" type="file" accept="image/*"></input>
|
||||||
|
<button id="upload_button" type="submit">Upload</button>
|
||||||
|
</form>
|
||||||
|
<dom-bind>
|
||||||
|
<template>
|
||||||
|
<vaadin-upload id="file_upload" files="{%raw%}{{files}}{%endraw%}" target="" method="POST" form-data-name="file" nodrop="[[nodrop]]">
|
||||||
|
<style is="custom-style">
|
||||||
|
[nodrop] [part="upload-button"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
[nodrop] #addFiles {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div slot="drop-label-icon"></div>
|
||||||
|
<span slot="drop-label" class="font-headline">or drag a file here</span>
|
||||||
|
<div slot="file-list">
|
||||||
|
<template is="dom-repeat" items="[[files]]" as="file">
|
||||||
|
<vaadin-permalinked-upload-file file="[[file]]"></vaadin-permalinked-upload-file>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</vaadin-upload>
|
||||||
|
</template>
|
||||||
|
</dom-bind>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function isES6()
|
||||||
|
{
|
||||||
|
try { Function("() => {};"); return true; }
|
||||||
|
catch(exception) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('WebComponentsReady', function() {
|
||||||
|
// Remove noscript / dinosaur componenets if both webcomponents and es6 avalible
|
||||||
|
if (isES6()){
|
||||||
|
var noscripts = document.querySelectorAll('.noscript');
|
||||||
|
for (var i = noscripts.length-1; i >= 0; i--){
|
||||||
|
noscripts[i].parentNode.removeChild(noscripts[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Abort polymer loading to fix IE become ing responsive for a bit
|
||||||
|
}
|
||||||
|
|
||||||
|
var upload = document.querySelector('vaadin-upload#file_upload');
|
||||||
|
var binder = document.querySelector('dom-bind');
|
||||||
|
|
||||||
|
upload.addEventListener('upload-response', function(event) {
|
||||||
|
response = JSON.parse(event.detail.xhr.response);
|
||||||
|
console.log(response);
|
||||||
|
if (response.status == 'success'){
|
||||||
|
event.detail.file.url = response.link;
|
||||||
|
} else {
|
||||||
|
event.detail.file.message = response.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binder.files = [];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Reference in New Issue