add user config page and admin menu
This commit is contained in:
20
go.mod
Normal file
20
go.mod
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module fornaxian.com/pixeldrain-web
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681
|
||||||
|
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70
|
||||||
|
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3
|
||||||
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
github.com/k0kubun/pp v3.0.1+incompatible // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.2
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
|
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8
|
||||||
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
|
||||||
|
gopkg.in/russross/blackfriday.v2 v2.0.0
|
||||||
|
)
|
115
go.sum
Normal file
115
go.sum
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
fornaxian.com/pixeldrain-api v0.0.0-20191213180911-5823f7064801/go.mod h1:bhJgwXdFTFrZUdQ+vBXzUQiAhvuFnEgZgxeXBhWddLU=
|
||||||
|
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681 h1:tLIj6RhZHsJW1riDUZj7c4UQ3Gojl8Sh6CHj9cC4nzQ=
|
||||||
|
fornaxian.com/pixeldrain-api v0.0.0-20191216095319-0533f903c681/go.mod h1:HooOANQfnTseH1XR/kJ2R8sKpPM68GdzHQyHiUlaZqI=
|
||||||
|
fornaxian.com/pixeldrain-web v0.0.0-20191211101305-cc3c5d58e432/go.mod h1:UwobVC5YVBOtfTc6eBSBjD2tdLQ+xYYp7+web6nCjD0=
|
||||||
|
fornaxian.com/pixelstore v0.0.0-20191212224440-0453456df082/go.mod h1:saC4IwAOpIip/M7fURYMTcUo5fhwp/m5GmvoQucSwLQ=
|
||||||
|
fornaxian.com/pixelstore v0.0.0-20191213205107-7cc26c05cb69/go.mod h1:6cdebY9AhneUTQZQ+dyXnIZmMYxCAVgJ5lCeUTWPR8M=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70 h1:yRkXab8h+BAWEphLE0qexJVxIOdPgw+3T9VSLuyPXus=
|
||||||
|
github.com/Fornaxian/config v0.0.0-20180915150834-ac41cf746a70/go.mod h1:Ig5am30IOP/eqsjogI1TuSlOTIeTPHoMOpYYM1bisww=
|
||||||
|
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3 h1:PfKr7anK3z4kLG9V6BbbKOVFhVaGEAJi4HxXCAa+QeU=
|
||||||
|
github.com/Fornaxian/log v0.0.0-20190617093801-1c7ce9a7c9b3/go.mod h1:jdnyerqAlXJJpQmpyrdmSYMitRaRZ8RejEXuXz6n5QY=
|
||||||
|
github.com/Fornaxian/unifilter v0.0.0-20180623154047-e65e144d5942/go.mod h1:ofV5syadd2nI4gOc/rP1yPnXkARgm+E1D/U38mbUj44=
|
||||||
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dchest/threefish v0.0.0-20120919164726-3ecf4c494abf/go.mod h1:bXVurdTuvOiJu7NHALemFe0JMvC2UmwYHW+7fcZaZ2M=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
|
||||||
|
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
|
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||||
|
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||||
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8 h1:gw/M1/pCu7oELGHZ6rvktNmMbdWhf9kHc7WYrbLeKdo=
|
||||||
|
github.com/timakin/gonvert v0.0.0-20170112000238-5dce59dbd0d8/go.mod h1:oqLl90kSlp4+8wMQKql9ZdQGa4/5pVCxOOpTVWkoyV0=
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/xtaci/smux v1.3.3/go.mod h1:f+nYm6SpuHMy/SH0zpbvAFHT1QoMcgLOsWcFip5KfPw=
|
||||||
|
gitlab.com/NebulousLabs/Sia v1.4.1/go.mod h1:pmBBguXJl2nxajST2OtRv0FOIMSggtn5evGpE9Pju3Y=
|
||||||
|
gitlab.com/NebulousLabs/demotemutex v0.0.0-20151003192217-235395f71c40/go.mod h1:HfnnxM8isYA7FUlqS5h34XTeiBhPtcuCquVujKsn9aw=
|
||||||
|
gitlab.com/NebulousLabs/entropy-mnemonics v0.0.0-20181018051301-7532f67e3500/go.mod h1:4koft3fRXTETovKPTeX/Aggj+ajCGWCcuuBBc598Pcs=
|
||||||
|
gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8=
|
||||||
|
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA=
|
||||||
|
gitlab.com/NebulousLabs/go-upnp v0.0.0-20181011194642-3a71999ed0d3/go.mod h1:sleOmkovWsDEQVYXmOJhx69qheoMTmCuPYyiCFCihlg=
|
||||||
|
gitlab.com/NebulousLabs/merkletree v0.0.0-20190207030457-bc4a11e31a0d/go.mod h1:xItahGeKIkh9BQfxDEX6O3eWxOxbLBPX738sXm0uVaQ=
|
||||||
|
gitlab.com/NebulousLabs/ratelimit v0.0.0-20180716154200-1308156c2eaf/go.mod h1:vowDA1cdvtWW678ugB7L/yKT2pCN37aH6zYp9NF5Isc=
|
||||||
|
gitlab.com/NebulousLabs/threadgroup v0.0.0-20180716154133-88a11db9e46c/go.mod h1:w05nvlkvHlk3Vfc7mcU29Toic1X0BcYUnKoTHS0ea2Y=
|
||||||
|
gitlab.com/NebulousLabs/writeaheadlog v0.0.0-20190703190009-cb822c37bc94/go.mod h1:Lhpa9AcbWcYKcc4amZsOHqJdQglnkWrGuUI68XC7U2Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||||
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||||
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
|
||||||
|
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@@ -1,5 +1,7 @@
|
|||||||
package pixelapi
|
package pixelapi
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
// IsAdmin is the response to the /admin/is_admin API
|
// IsAdmin is the response to the /admin/is_admin API
|
||||||
type IsAdmin struct {
|
type IsAdmin struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
@@ -14,3 +16,31 @@ func (p *PixelAPI) UserIsAdmin() (resp IsAdmin, err error) {
|
|||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminGlobal is a global setting in pixeldrain's back-end
|
||||||
|
type AdminGlobal struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminGlobals is an array of globals
|
||||||
|
type AdminGlobals struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Globals []AdminGlobal `json:"globals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminGetGlobals returns if the logged in user is an admin user
|
||||||
|
func (p *PixelAPI) AdminGetGlobals() (resp AdminGlobals, err error) {
|
||||||
|
if err = p.jsonRequest("GET", p.apiEndpoint+"/admin/globals", &resp); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminSetGlobals returns if the logged in user is an admin user
|
||||||
|
func (p *PixelAPI) AdminSetGlobals(key, value string) (resp SuccessResponse, err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("key", key)
|
||||||
|
form.Add("value", value)
|
||||||
|
return resp, p.form("POST", p.apiEndpoint+"/admin/globals", form, &resp, true)
|
||||||
|
}
|
||||||
|
@@ -164,7 +164,6 @@ func (p *PixelAPI) form(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error {
|
func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool) error {
|
||||||
var jdec = json.NewDecoder(resp.Body)
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Test for client side and server side errors
|
// Test for client side and server side errors
|
||||||
@@ -172,8 +171,7 @@ func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool
|
|||||||
var errResp = Error{
|
var errResp = Error{
|
||||||
ReqError: false,
|
ReqError: false,
|
||||||
}
|
}
|
||||||
err = jdec.Decode(&errResp)
|
if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error("Can't decode this: %v", err)
|
log.Error("Can't decode this: %v", err)
|
||||||
return Error{
|
return Error{
|
||||||
ReqError: true,
|
ReqError: true,
|
||||||
@@ -185,8 +183,11 @@ func parseJSONResponse(resp *http.Response, target interface{}, catchErrors bool
|
|||||||
return errResp
|
return errResp
|
||||||
}
|
}
|
||||||
|
|
||||||
err = jdec.Decode(target)
|
if target == nil {
|
||||||
if err != nil {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(target); err != nil {
|
||||||
r, _ := ioutil.ReadAll(resp.Body)
|
r, _ := ioutil.ReadAll(resp.Body)
|
||||||
log.Error("Can't decode this: %v. %s", err, r)
|
log.Error("Can't decode this: %v. %s", err, r)
|
||||||
return Error{
|
return Error{
|
||||||
|
@@ -3,6 +3,7 @@ package pixelapi
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Registration is the response to the UserRegister API. The register API can
|
// Registration is the response to the UserRegister API. The register API can
|
||||||
@@ -66,6 +67,7 @@ func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp *Log
|
|||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserInfo returns information about the logged in user. Requires an API key
|
// UserInfo returns information about the logged in user. Requires an API key
|
||||||
@@ -80,13 +82,8 @@ func (p *PixelAPI) UserInfo() (resp *UserInfo, err error) {
|
|||||||
|
|
||||||
// UserSessionDestroy destroys an API key so it can no longer be used to perform
|
// UserSessionDestroy destroys an API key so it can no longer be used to perform
|
||||||
// actions
|
// actions
|
||||||
func (p *PixelAPI) UserSessionDestroy(key string) (resp *SuccessResponse, err error) {
|
func (p *PixelAPI) UserSessionDestroy(key string) (err error) {
|
||||||
resp = &SuccessResponse{}
|
return p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", nil)
|
||||||
err = p.jsonRequest("DELETE", p.apiEndpoint+"/user/session", resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserFiles struct {
|
type UserFiles struct {
|
||||||
@@ -114,25 +111,64 @@ type UserLists struct {
|
|||||||
|
|
||||||
func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) {
|
func (p *PixelAPI) UserLists(page, limit int) (resp *UserLists, err error) {
|
||||||
resp = &UserLists{Lists: make([]List, 0)}
|
resp = &UserLists{Lists: make([]List, 0)}
|
||||||
err = p.jsonRequest(
|
if err = p.jsonRequest(
|
||||||
"GET",
|
"GET",
|
||||||
fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit),
|
fmt.Sprintf("%s/user/lists?page=%d&limit=%d", p.apiEndpoint, page, limit),
|
||||||
resp,
|
resp,
|
||||||
)
|
); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (resp *SuccessResponse, err error) {
|
func (p *PixelAPI) UserPasswordSet(oldPW, newPW string) (err error) {
|
||||||
resp = &SuccessResponse{}
|
|
||||||
var form = url.Values{}
|
var form = url.Values{}
|
||||||
form.Add("old_password", oldPW)
|
form.Add("old_password", oldPW)
|
||||||
form.Add("new_password", newPW)
|
form.Add("new_password", newPW)
|
||||||
err = p.form("PUT", p.apiEndpoint+"/user/password", form, resp, true)
|
return p.form("PUT", p.apiEndpoint+"/user/password", form, nil, true)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
// UserEmailReset starts the e-mail change process. An email will be sent to the
|
||||||
return resp, nil
|
// new address to verify that it's real. Once the link in the e-mail is clicked
|
||||||
|
// the key it contains can be sent to the API with UserEmailResetConfirm and the
|
||||||
|
// change will be applied
|
||||||
|
func (p *PixelAPI) UserEmailReset(email string, delete bool) (err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("new_email", email)
|
||||||
|
form.Add("delete", strconv.FormatBool(delete))
|
||||||
|
return p.form("PUT", p.apiEndpoint+"/user/email_reset", form, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserEmailResetConfirm finishes process of changing a user's e-mail address
|
||||||
|
func (p *PixelAPI) UserEmailResetConfirm(key string) (err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("key", key)
|
||||||
|
return p.form("PUT", p.apiEndpoint+"/user/email_reset_cofirm", form, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPasswordReset starts the password reset process. An email will be sent
|
||||||
|
// the user to verify that it really wanted to reset the password. Once the link
|
||||||
|
// in the e-mail is clicked the key it contains can be sent to the API with
|
||||||
|
// UserPasswordResetConfirm and a new password can be set
|
||||||
|
func (p *PixelAPI) UserPasswordReset(email string, recaptchaResponse string) (err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("email", email)
|
||||||
|
form.Add("recaptcha_response", recaptchaResponse)
|
||||||
|
return p.form("PUT", p.apiEndpoint+"/user/password_reset", form, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPasswordResetConfirm finishes process of resetting a user's password. If
|
||||||
|
// the key is valid the new_password parameter will be saved as the new password
|
||||||
|
func (p *PixelAPI) UserPasswordResetConfirm(key string, newPassword string) (err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("key", key)
|
||||||
|
form.Add("new_password", newPassword)
|
||||||
|
return p.form("PUT", p.apiEndpoint+"/user/password_reset_confirm", form, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserSetUsername changes the user's username.
|
||||||
|
func (p *PixelAPI) UserSetUsername(username string) (err error) {
|
||||||
|
var form = url.Values{}
|
||||||
|
form.Add("new_username", username)
|
||||||
|
return p.form("PUT", p.apiEndpoint+"/user/username", form, nil, true)
|
||||||
}
|
}
|
||||||
|
37
res/template/account/email_confirm.html
Normal file
37
res/template/account/email_confirm.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{{define "email_confirm"}}<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{template "meta_tags" "E-mail verification"}}
|
||||||
|
{{template "user_style" .}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{template "page_top" .}}
|
||||||
|
|
||||||
|
<div class="page_content">
|
||||||
|
<div class="limit_width">
|
||||||
|
{{if eq .Other "success"}}
|
||||||
|
<h1>Success!</h1>
|
||||||
|
<p>
|
||||||
|
Your account's e-mail address has been updated.
|
||||||
|
</p>
|
||||||
|
{{else if eq .Other "not_found"}}
|
||||||
|
<h1>E-mail change failed</h1>
|
||||||
|
<p>
|
||||||
|
This e-mail change request does not exist or has expired.
|
||||||
|
Please try again if you still want to change your e-mail
|
||||||
|
address.
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>
|
||||||
|
Something went wrong while processing this request. Please
|
||||||
|
try again later.
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "page_bottom" .}}
|
||||||
|
{{template "analytics"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{end}}
|
@@ -12,11 +12,12 @@
|
|||||||
<h1>Welcome home, {{.Username}}!</h1>
|
<h1>Welcome home, {{.Username}}!</h1>
|
||||||
|
|
||||||
<div class="page_content"><div class="limit_width">
|
<div class="page_content"><div class="limit_width">
|
||||||
<h2>Actions</h2>
|
<h2>Account information</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/user/change_password" class="button">Change my password</a></li>
|
<li>Username: {{.Username}}</li>
|
||||||
<li><a href="/logout" class="button">Log out</a></li>
|
<li>E-mail address: {{.Email}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<a href="/user/settings" class="button">Update account settings</a>
|
||||||
|
|
||||||
<h2>Your most recently uploaded files:</h2>
|
<h2>Your most recently uploaded files:</h2>
|
||||||
<div class="highlight_dark">
|
<div class="highlight_dark">
|
||||||
|
@@ -8,14 +8,11 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{template "page_top" .}}
|
{{template "page_top" .}}
|
||||||
<h1>User configuration</h1>
|
<h1>{{.Title}}</h1>
|
||||||
<div class="page_content"><div class="limit_width">
|
<div class="page_content"><div class="limit_width">
|
||||||
|
{{template "form" .Other.PasswordForm}}
|
||||||
<p>What would you like to do?</p>
|
{{template "form" .Other.EmailForm}}
|
||||||
<ul>
|
{{template "form" .Other.UsernameForm}}
|
||||||
<li><a href="/user/change_password">Change my password</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div></div>
|
</div></div>
|
||||||
{{template "page_bottom" .}}
|
{{template "page_bottom" .}}
|
||||||
{{template "analytics"}}
|
{{template "analytics"}}
|
||||||
|
@@ -77,7 +77,14 @@
|
|||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function(value, index, values) {
|
callback: function(value, index, values) {
|
||||||
return Math.round((value*8/1e6)/(interval*60)) + " Mbps";
|
if (value > 1e12) {
|
||||||
|
return Math.round(value/1e9)/1e3 + " TB";
|
||||||
|
} else if (value > 1e9) {
|
||||||
|
return Math.round(value/1e6)/1e3 + " GB";
|
||||||
|
} else if (value > 1e6) {
|
||||||
|
return Math.round(value/1e3)/1e3 + " MB";
|
||||||
|
}
|
||||||
|
return value/1e3 + " kB";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
gridLines: {
|
gridLines: {
|
||||||
@@ -127,6 +134,10 @@
|
|||||||
setData();
|
setData();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div class="limit_width">
|
||||||
|
<a href="/admin/globals">Update global settings</a>
|
||||||
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<h1 style="text-align: center;">;)</h1>
|
<h1 style="text-align: center;">;)</h1>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@@ -1,24 +1,23 @@
|
|||||||
{{define "form"}}
|
{{define "form"}}
|
||||||
<h1>{{.Title}}</h1>
|
<h1>{{.Title}}</h1>
|
||||||
{{.PreFormHTML}}
|
{{.PreFormHTML}}
|
||||||
{{if eq .Submitted true}}
|
|
||||||
{{if eq .SubmitSuccess true}}
|
|
||||||
<div id="submit_result" class="highlight_green">
|
|
||||||
{{index .SubmitMessages 0}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div id="submit_result" class="highlight_red">
|
|
||||||
Something went wrong, please correct these errors before continuing:<br/>
|
|
||||||
<ul>
|
|
||||||
{{range $msg := .SubmitMessages}}
|
|
||||||
<li>{{$msg}}</li>
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<form class="highlight_dark" method="POST">
|
<form class="highlight_dark" method="POST">
|
||||||
|
{{if eq .Submitted true}}
|
||||||
|
{{if eq .SubmitSuccess true}}
|
||||||
|
<div id="submit_result" class="highlight_green">
|
||||||
|
{{index .SubmitMessages 0}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div id="submit_result" class="highlight_red">
|
||||||
|
Something went wrong, please correct these errors before continuing:<br/>
|
||||||
|
<ul>
|
||||||
|
{{range $msg := .SubmitMessages}}
|
||||||
|
<li>{{$msg}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
<input type="text" name="form" value="{{.Name}}" style="display: none;" readonly="readonly"/>
|
<input type="text" name="form" value="{{.Name}}" style="display: none;" readonly="readonly"/>
|
||||||
{{if ne .Username ""}}
|
{{if ne .Username ""}}
|
||||||
<!-- The invisible username field is so browsers know which user the form was for -->
|
<!-- The invisible username field is so browsers know which user the form was for -->
|
||||||
@@ -27,23 +26,32 @@
|
|||||||
<table class="form">
|
<table class="form">
|
||||||
{{range $index, $field := .Fields}}
|
{{range $index, $field := .Fields}}
|
||||||
<tr class="form">
|
<tr class="form">
|
||||||
<td>{{$field.Label}}</td>
|
{{if eq $field.Type "textarea"}}
|
||||||
<td>
|
<td colspan="2">
|
||||||
{{if eq $field.Type "text"}}
|
{{$field.Label}}<br/>
|
||||||
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" class="form_input"/>
|
<textarea id="input_{{$field.Name}}" name="{{$field.Name}}" class="form_input" style="width: 100%; height: 5em; resize: vertical;">{{$field.DefaultValue}}</textarea>
|
||||||
{{else if eq $field.Type "username"}}
|
</td>
|
||||||
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" autocomplete="username" class="form_input"/>
|
{{else}}
|
||||||
{{else if eq $field.Type "email"}}
|
<td>{{$field.Label}}</td>
|
||||||
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="email" autocomplete="email" class="form_input"/>
|
<td>
|
||||||
{{else if eq $field.Type "current-password"}}
|
{{if eq $field.Type "text"}}
|
||||||
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="current-password" class="form_input"/>
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" class="form_input"/>
|
||||||
{{else if eq $field.Type "new-password"}}
|
{{else if eq $field.Type "number"}}
|
||||||
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="new-password" class="form_input"/>
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="number" class="form_input"/>
|
||||||
{{else if eq $field.Type "captcha"}}
|
{{else if eq $field.Type "username"}}
|
||||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="text" autocomplete="username" class="form_input"/>
|
||||||
<div class="g-recaptcha" data-theme="dark" data-sitekey="{{$field.CaptchaSiteKey}}"></div>
|
{{else if eq $field.Type "email"}}
|
||||||
{{end}}
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="email" autocomplete="email" class="form_input"/>
|
||||||
</td>
|
{{else if eq $field.Type "current-password"}}
|
||||||
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="current-password" class="form_input"/>
|
||||||
|
{{else if eq $field.Type "new-password"}}
|
||||||
|
<input id="input_{{$field.Name}}" name="{{$field.Name}}" value="{{$field.DefaultValue}}" type="password" autocomplete="new-password" class="form_input"/>
|
||||||
|
{{else if eq $field.Type "captcha"}}
|
||||||
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||||
|
<div class="g-recaptcha" data-theme="dark" data-sitekey="{{$field.CaptchaSiteKey}}"></div>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
{{if or (ne $field.Description "") (eq $field.Separator true)}}
|
{{if or (ne $field.Description "") (eq $field.Separator true)}}
|
||||||
<tr class="form">
|
<tr class="form">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
|
97
webcontroller/admin_panel.go
Normal file
97
webcontroller/admin_panel.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package webcontroller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||||
|
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||||
|
"github.com/Fornaxian/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||||
|
if isAdmin, err := td.PixelAPI.UserIsAdmin(); err != nil {
|
||||||
|
td.Title = err.Error()
|
||||||
|
return forms.Form{Title: td.Title}
|
||||||
|
} else if !isAdmin.IsAdmin {
|
||||||
|
td.Title = ";)"
|
||||||
|
return forms.Form{Title: td.Title}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.Title = "Pixeldrain global configuration"
|
||||||
|
f = forms.Form{
|
||||||
|
Name: "admin_globals",
|
||||||
|
Title: td.Title,
|
||||||
|
PreFormHTML: template.HTML("<p>Careful! The slightest typing error could bring the whole website down</p>"),
|
||||||
|
BackLink: "/admin",
|
||||||
|
SubmitLabel: "Submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
globals, err := td.PixelAPI.AdminGetGlobals()
|
||||||
|
if err != nil {
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(err.Error())}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
var globalsMap = make(map[string]string)
|
||||||
|
for _, v := range globals.Globals {
|
||||||
|
f.Fields = append(f.Fields, forms.Field{
|
||||||
|
Name: v.Key,
|
||||||
|
DefaultValue: v.Value,
|
||||||
|
Label: v.Key,
|
||||||
|
Type: func() forms.FieldType {
|
||||||
|
switch v.Key {
|
||||||
|
case
|
||||||
|
"email_address_change_body",
|
||||||
|
"email_password_reset_body":
|
||||||
|
return forms.FieldTypeTextarea
|
||||||
|
case
|
||||||
|
"api_ratelimit_limit",
|
||||||
|
"api_ratelimit_rate",
|
||||||
|
"cron_interval_seconds",
|
||||||
|
"file_inactive_expiry_days",
|
||||||
|
"max_file_size",
|
||||||
|
"pixelstore_min_redundancy":
|
||||||
|
return forms.FieldTypeNumber
|
||||||
|
default:
|
||||||
|
return forms.FieldTypeText
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
globalsMap[v.Key] = v.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ReadInput(r) {
|
||||||
|
var successfulUpdates = 0
|
||||||
|
for k, v := range f.Fields {
|
||||||
|
if v.EnteredValue == globalsMap[v.Name] {
|
||||||
|
continue // Change changes, no need to update
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value changed, try to update global setting
|
||||||
|
if _, err = td.PixelAPI.AdminSetGlobals(v.Name, v.EnteredValue); err != nil {
|
||||||
|
if apiErr, ok := err.(pixelapi.Error); ok {
|
||||||
|
f.SubmitMessages = append(f.SubmitMessages, template.HTML(apiErr.Message))
|
||||||
|
} else {
|
||||||
|
log.Error("%s", err)
|
||||||
|
f.SubmitMessages = append(f.SubmitMessages, template.HTML(
|
||||||
|
fmt.Sprintf("Failed to set '%s': %s", v.Name, err),
|
||||||
|
))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.Fields[k].DefaultValue = v.EnteredValue
|
||||||
|
successfulUpdates++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(f.SubmitMessages) == 0 {
|
||||||
|
// Request was a success
|
||||||
|
f.SubmitSuccess = true
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(
|
||||||
|
fmt.Sprintf("Success! %d values updated", successfulUpdates),
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
@@ -79,6 +79,8 @@ type FieldType string
|
|||||||
// Fields which can be in a form
|
// Fields which can be in a form
|
||||||
const (
|
const (
|
||||||
FieldTypeText FieldType = "text"
|
FieldTypeText FieldType = "text"
|
||||||
|
FieldTypeTextarea FieldType = "textarea"
|
||||||
|
FieldTypeNumber FieldType = "number"
|
||||||
FieldTypeUsername FieldType = "username"
|
FieldTypeUsername FieldType = "username"
|
||||||
FieldTypeEmail FieldType = "email"
|
FieldTypeEmail FieldType = "email"
|
||||||
FieldTypeCurrentPassword FieldType = "current-password"
|
FieldTypeCurrentPassword FieldType = "current-password"
|
||||||
|
@@ -16,6 +16,7 @@ import (
|
|||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
Authenticated bool
|
Authenticated bool
|
||||||
Username string
|
Username string
|
||||||
|
Email string
|
||||||
UserAgent string
|
UserAgent string
|
||||||
UserStyle template.CSS
|
UserStyle template.CSS
|
||||||
APIEndpoint template.URL
|
APIEndpoint template.URL
|
||||||
@@ -67,6 +68,7 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
|
|||||||
// Authentication succeeded
|
// Authentication succeeded
|
||||||
t.Authenticated = true
|
t.Authenticated = true
|
||||||
t.Username = uinf.Username
|
t.Username = uinf.Username
|
||||||
|
t.Email = uinf.Email
|
||||||
} else {
|
} else {
|
||||||
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
|
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,8 @@ func (wc *WebController) serveLogout(
|
|||||||
) {
|
) {
|
||||||
if key, err := wc.getAPIKey(r); err == nil {
|
if key, err := wc.getAPIKey(r); err == nil {
|
||||||
var api = pixelapi.New(wc.conf.APIURLInternal, key)
|
var api = pixelapi.New(wc.conf.APIURLInternal, key)
|
||||||
_, err1 := api.UserSessionDestroy(key)
|
if err = api.UserSessionDestroy(key); err != nil {
|
||||||
if err1 != nil {
|
log.Warn("logout failed for session '%s': %s", key, err)
|
||||||
log.Warn("logout failed for session '%s': %s", key, err1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +150,12 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
|
|||||||
BackLink: "/",
|
BackLink: "/",
|
||||||
SubmitLabel: "Login",
|
SubmitLabel: "Login",
|
||||||
PostFormHTML: template.HTML(
|
PostFormHTML: template.HTML(
|
||||||
`<br/>If you don't have a pixeldrain account yet, you can ` +
|
`<p>If you don't have a pixeldrain account yet, you can ` +
|
||||||
`<a href="/register">register here</a>. No e-mail address is ` +
|
`<a href="/register">register here</a>. No e-mail address is ` +
|
||||||
`required.<br/>`,
|
`required.</p>` +
|
||||||
|
`<p>Forgot your password? If your account has a valid e-mail ` +
|
||||||
|
`address you can <a href="/password_reset">request a new ` +
|
||||||
|
`password here</a>.</p>`,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,45 +186,35 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||||
td.Title = "Change Password"
|
td.Title = "Recover lost password"
|
||||||
f = forms.Form{
|
f = forms.Form{
|
||||||
Name: "password_change",
|
Name: "password_reset",
|
||||||
Title: td.Title,
|
Title: td.Title,
|
||||||
Fields: []forms.Field{
|
Fields: []forms.Field{
|
||||||
{
|
{
|
||||||
Name: "old_password",
|
Name: "email",
|
||||||
Label: "Old Password",
|
Label: "E-mail address",
|
||||||
Type: forms.FieldTypeCurrentPassword,
|
Description: "we will send a password reset link to this " +
|
||||||
|
"e-mail address",
|
||||||
|
Separator: true,
|
||||||
|
Type: forms.FieldTypeEmail,
|
||||||
}, {
|
}, {
|
||||||
Name: "new_password1",
|
Name: "recaptcha_response",
|
||||||
Label: "New Password",
|
Label: "Turing test (click the white box)",
|
||||||
Type: forms.FieldTypeNewPassword,
|
Description: "the reCaptcha turing test verifies that you " +
|
||||||
}, {
|
"are not an evil robot that is trying hijack accounts",
|
||||||
Name: "new_password2",
|
Separator: true,
|
||||||
Label: "New Password verification",
|
Type: forms.FieldTypeCaptcha,
|
||||||
Type: forms.FieldTypeCurrentPassword,
|
CaptchaSiteKey: wc.captchaKey(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BackLink: "/user",
|
BackLink: "/login",
|
||||||
SubmitLabel: "Submit",
|
SubmitLabel: "Submit",
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.ReadInput(r) {
|
if f.ReadInput(r) {
|
||||||
if f.FieldVal("new_password1") != f.FieldVal("new_password2") {
|
if err := td.PixelAPI.UserPasswordReset(f.FieldVal("email"), f.FieldVal("recaptcha_response")); err != nil {
|
||||||
f.SubmitMessages = []template.HTML{
|
|
||||||
"Password verification failed. Please enter the same " +
|
|
||||||
"password in both new password fields"}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passwords match, send the request and fill in the response in the
|
|
||||||
// form
|
|
||||||
_, err := td.PixelAPI.UserPasswordSet(
|
|
||||||
f.FieldVal("old_password"),
|
|
||||||
f.FieldVal("new_password1"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if apiErr, ok := err.(pixelapi.Error); ok {
|
if apiErr, ok := err.(pixelapi.Error); ok {
|
||||||
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
|
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
|
||||||
} else {
|
} else {
|
||||||
@@ -230,9 +222,8 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f form
|
|||||||
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Request was a success
|
|
||||||
f.SubmitSuccess = true
|
f.SubmitSuccess = true
|
||||||
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"}
|
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
|
188
webcontroller/user_settings.go
Normal file
188
webcontroller/user_settings.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package webcontroller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||||
|
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||||
|
"github.com/Fornaxian/log"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (wc *WebController) serveUserSettings(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
p httprouter.Params,
|
||||||
|
) {
|
||||||
|
td := wc.newTemplateData(w, r)
|
||||||
|
|
||||||
|
if !td.Authenticated {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
td.Title = "Account settings"
|
||||||
|
td.Other = struct {
|
||||||
|
PasswordForm forms.Form
|
||||||
|
EmailForm forms.Form
|
||||||
|
UsernameForm forms.Form
|
||||||
|
}{
|
||||||
|
PasswordForm: wc.passwordForm(td, r),
|
||||||
|
EmailForm: wc.emailForm(td, r),
|
||||||
|
UsernameForm: wc.usernameForm(td, r),
|
||||||
|
}
|
||||||
|
wc.templates.Get().ExecuteTemplate(w, "user_settings", td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||||
|
f = forms.Form{
|
||||||
|
Name: "password_change",
|
||||||
|
Title: "Change password",
|
||||||
|
Fields: []forms.Field{
|
||||||
|
{
|
||||||
|
Name: "old_password",
|
||||||
|
Label: "Old Password",
|
||||||
|
Type: forms.FieldTypeCurrentPassword,
|
||||||
|
}, {
|
||||||
|
Name: "new_password1",
|
||||||
|
Label: "New Password",
|
||||||
|
Type: forms.FieldTypeNewPassword,
|
||||||
|
}, {
|
||||||
|
Name: "new_password2",
|
||||||
|
Label: "New Password again",
|
||||||
|
Description: "we need you to repeat your password so you " +
|
||||||
|
"won't be locked out of your account if you make a " +
|
||||||
|
"typing error",
|
||||||
|
Type: forms.FieldTypeNewPassword,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SubmitLabel: "Submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ReadInput(r) {
|
||||||
|
if f.FieldVal("new_password1") != f.FieldVal("new_password2") {
|
||||||
|
f.SubmitMessages = []template.HTML{
|
||||||
|
"Password verification failed. Please enter the same " +
|
||||||
|
"password in both new password fields"}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passwords match, send the request and fill in the response in the
|
||||||
|
// form
|
||||||
|
if err := td.PixelAPI.UserPasswordSet(
|
||||||
|
f.FieldVal("old_password"),
|
||||||
|
f.FieldVal("new_password1"),
|
||||||
|
); err != nil {
|
||||||
|
if apiErr, ok := err.(pixelapi.Error); ok {
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
|
||||||
|
} else {
|
||||||
|
log.Error("%s", err)
|
||||||
|
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Request was a success
|
||||||
|
f.SubmitSuccess = true
|
||||||
|
f.SubmitMessages = []template.HTML{"Success! Your password has been updated"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||||
|
f = forms.Form{
|
||||||
|
Name: "email_change",
|
||||||
|
Title: "Change e-mail address",
|
||||||
|
Fields: []forms.Field{
|
||||||
|
{
|
||||||
|
Name: "new_email",
|
||||||
|
Label: "New e-mail address",
|
||||||
|
Description: "we will send an e-mail to the new address to " +
|
||||||
|
"verify that it's real. The address will be saved once " +
|
||||||
|
"the link in the message is clicked. If the e-mail " +
|
||||||
|
"doesn't arrive right away please check your spam box too",
|
||||||
|
Type: forms.FieldTypeEmail,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SubmitLabel: "Submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ReadInput(r) {
|
||||||
|
if err := td.PixelAPI.UserEmailReset(
|
||||||
|
f.FieldVal("new_email"),
|
||||||
|
false,
|
||||||
|
); err != nil {
|
||||||
|
if apiErr, ok := err.(pixelapi.Error); ok {
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
|
||||||
|
} else {
|
||||||
|
log.Error("%s", err)
|
||||||
|
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Request was a success
|
||||||
|
f.SubmitSuccess = true
|
||||||
|
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WebController) serveEmailConfirm(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
p httprouter.Params,
|
||||||
|
) {
|
||||||
|
var status string
|
||||||
|
if key, err := wc.getAPIKey(r); err == nil {
|
||||||
|
err = pixelapi.New(wc.conf.APIURLInternal, key).UserEmailResetConfirm(r.FormValue("key"))
|
||||||
|
if err != nil && err.Error() == "not_found" {
|
||||||
|
status = "not_found"
|
||||||
|
} else if err != nil {
|
||||||
|
status = "internal_error"
|
||||||
|
} else {
|
||||||
|
status = "success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td := wc.newTemplateData(w, r)
|
||||||
|
td.Other = status
|
||||||
|
|
||||||
|
wc.templates.Get().ExecuteTemplate(w, "email_confirm", td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||||
|
f = forms.Form{
|
||||||
|
Name: "username_change",
|
||||||
|
Title: "Change username",
|
||||||
|
Fields: []forms.Field{
|
||||||
|
{
|
||||||
|
Name: "new_username",
|
||||||
|
Label: "New username",
|
||||||
|
Description: "changing your username also changes the name " +
|
||||||
|
"used to log in. If you forget your username you can " +
|
||||||
|
"still log in using your e-mail address if you have one " +
|
||||||
|
"configured",
|
||||||
|
Type: forms.FieldTypeUsername,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SubmitLabel: "Submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ReadInput(r) {
|
||||||
|
if err := td.PixelAPI.UserSetUsername(f.FieldVal("new_username")); err != nil {
|
||||||
|
if apiErr, ok := err.(pixelapi.Error); ok {
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(apiErr.Message)}
|
||||||
|
} else {
|
||||||
|
log.Error("%s", err)
|
||||||
|
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Request was a success
|
||||||
|
f.SubmitSuccess = true
|
||||||
|
f.SubmitMessages = []template.HTML{template.HTML(
|
||||||
|
"Success! You are now " + f.FieldVal("new_username"),
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
@@ -78,7 +78,9 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
|
|||||||
r.GET(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
|
r.GET(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
|
||||||
r.POST(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
|
r.POST(p+"/register" /* */, wc.serveForm(wc.registerForm, false))
|
||||||
r.GET(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
|
r.GET(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
|
||||||
r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
|
r.POST(p+"/login" /* */, wc.serveForm(wc.loginForm, false))
|
||||||
|
r.GET(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
|
||||||
|
r.POST(p+"/password_reset" /* */, wc.serveForm(wc.passwordResetForm, false))
|
||||||
r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true))
|
r.GET(p+"/logout" /* */, wc.serveTemplate("logout", true))
|
||||||
r.POST(p+"/logout" /* */, wc.serveLogout)
|
r.POST(p+"/logout" /* */, wc.serveLogout)
|
||||||
r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true))
|
r.GET(p+"/user" /* */, wc.serveTemplate("user_home", true))
|
||||||
@@ -87,11 +89,14 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
|
|||||||
r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true))
|
r.GET(p+"/user/filemanager" /**/, wc.serveTemplate("file_manager", true))
|
||||||
|
|
||||||
// User account settings
|
// User account settings
|
||||||
r.GET(p+"/user/settings" /* */, wc.serveTemplate("user_settings", true))
|
r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
|
||||||
r.GET(p+"/user/change_password" /* */, wc.serveForm(wc.passwordForm, true))
|
r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
|
||||||
r.POST(p+"/user/change_password" /**/, wc.serveForm(wc.passwordForm, true))
|
r.GET(p+"/user/confirm_email" /**/, wc.serveEmailConfirm)
|
||||||
|
|
||||||
r.GET(p+"/admin", wc.serveTemplate("admin_panel", true))
|
// Admin settings
|
||||||
|
r.GET(p+"/admin" /* */, wc.serveTemplate("admin_panel", true))
|
||||||
|
r.GET(p+"/admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true))
|
||||||
|
r.POST(p+"/admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true))
|
||||||
|
|
||||||
r.NotFound = http.HandlerFunc(wc.serveNotFound)
|
r.NotFound = http.HandlerFunc(wc.serveNotFound)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user