Initial Commit
This commit is contained in:
parent
32e120ddaa
commit
c6af685621
|
@ -0,0 +1,4 @@
|
||||||
|
/simpledash
|
||||||
|
/simpledash.toml
|
||||||
|
/sessions.db
|
||||||
|
.idea/
|
|
@ -0,0 +1,33 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Conf struct stores root of TOML config
|
||||||
|
type Conf struct {
|
||||||
|
Title string
|
||||||
|
Session SessionConf
|
||||||
|
AllowProxy []string
|
||||||
|
LoginRequired bool
|
||||||
|
Theme string
|
||||||
|
Users map[string]User
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionConf stores session configuration
|
||||||
|
type SessionConf struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// User stores user configuration from TOML
|
||||||
|
type User struct {
|
||||||
|
PasswordHash string
|
||||||
|
ShowPublic bool
|
||||||
|
Cards []Card `toml:"card"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card stores card configuration from TOML
|
||||||
|
type Card struct {
|
||||||
|
Type string `toml:"type"`
|
||||||
|
Title string `toml:"title"`
|
||||||
|
Description string `toml:"desc,omitempty"`
|
||||||
|
Icon string `toml:"icon,omitempty"`
|
||||||
|
URL string `toml:"url,omitempty"`
|
||||||
|
Data map[string]interface{} `toml:"data,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send error to HTTP response
|
||||||
|
func httpError(res http.ResponseWriter, errTmpl *template.Template, config Conf, statusCode int, reason string) {
|
||||||
|
// Write error code to response
|
||||||
|
res.WriteHeader(statusCode)
|
||||||
|
// Execute error template, outputting to response
|
||||||
|
err := errTmpl.Execute(res, map[string]interface{}{
|
||||||
|
"StatusCode": statusCode,
|
||||||
|
"Reason": reason,
|
||||||
|
"Config": config,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Error occurred while handling error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressRes(res http.ResponseWriter) *gzip.Writer {
|
||||||
|
// Set response header to reflect gzip compression
|
||||||
|
res.Header().Set("Content-Encoding", "gzip")
|
||||||
|
// Wrap response in gzip writer
|
||||||
|
return gzip.NewWriter(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a string slice contains a string
|
||||||
|
func strSlcContains(slice []string, str string) bool {
|
||||||
|
// For every value in slice
|
||||||
|
for _, val := range slice {
|
||||||
|
// If value is contained in provided string, return true
|
||||||
|
if strings.Contains(str, val) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
module simpledash
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.1.1 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.8.1
|
||||||
|
github.com/rs/zerolog v1.21.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/wader/gormstore/v2 v2.0.0
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||||
|
gorm.io/driver/sqlite v1.1.4
|
||||||
|
gorm.io/gorm v1.21.5
|
||||||
|
)
|
|
@ -0,0 +1,238 @@
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||||
|
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
|
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||||
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||||
|
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
|
||||||
|
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||||
|
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||||
|
github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw=
|
||||||
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
|
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||||
|
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.7 h1:6Pwi1b3QdY65cuv6SyVO0FgPd5J3Bl7wf/nQQjinHMA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
|
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
|
||||||
|
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
|
||||||
|
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||||
|
github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8=
|
||||||
|
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
|
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
|
||||||
|
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||||
|
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||||
|
github.com/jackc/pgx/v4 v4.10.1 h1:/6Q3ye4myIj6AaplUm+eRcz4OhK9HAvFf4ePsG40LJY=
|
||||||
|
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||||
|
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
|
||||||
|
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||||
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
|
||||||
|
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/wader/gormstore/v2 v2.0.0 h1:Idfd68RXNFibVmkNKgNv8l7BobUfyvwEm1gvWqeA/Yw=
|
||||||
|
github.com/wader/gormstore/v2 v2.0.0/go.mod h1:3BgNKFxRdVo2E4pq3e/eiim8qRDZzaveaIcIvu2T8r0=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.0.4 h1:TATTzt+kR+IV0+h3iUB3dHUe8omCvQ0rOkmfCsUBohk=
|
||||||
|
gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
|
||||||
|
gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=
|
||||||
|
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
|
||||||
|
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||||
|
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||||
|
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
gorm.io/gorm v1.21.5 h1:Qf3uCq1WR9lt9/udefdhaFcf+aAZ+mrDtfXTA+GB9Gc=
|
||||||
|
gorm.io/gorm v1.21.5/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
@ -0,0 +1,125 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Masterminds/sprig"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
"github.com/wader/gormstore/v2"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set global logger to ConsoleWriter
|
||||||
|
var Log = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Route *mux.Router
|
||||||
|
Templates map[string]*template.Template
|
||||||
|
Session *gormstore.Store
|
||||||
|
Config Conf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new empty map to store templates
|
||||||
|
var templates = map[string]*template.Template{}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create command-line flags
|
||||||
|
addr := flag.IPP("addr", "a", net.ParseIP("0.0.0.0"), "Bind address for HTTP server")
|
||||||
|
port := flag.IntP("port", "p", 8080, "Bind port for HTTP server")
|
||||||
|
config := flag.StringP("config", "c", "simpledash.toml", "TOML config file")
|
||||||
|
// Parse flags
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Create new router
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
|
// Create OS-specific glob for all templates
|
||||||
|
path := filepath.Join("resources", "templates", "*.html")
|
||||||
|
// Create OS-specific glob for all card templates
|
||||||
|
cardGlob := filepath.Join("resources", "templates", "cards", "*.html")
|
||||||
|
// Get all template paths
|
||||||
|
tmplMatches, _ := filepath.Glob(path)
|
||||||
|
cardMatches, _ := filepath.Glob(cardGlob)
|
||||||
|
matches := append(tmplMatches, cardMatches...)
|
||||||
|
// For each template path
|
||||||
|
for _, match := range matches {
|
||||||
|
// Get name of file without path or extension
|
||||||
|
fileName := strings.TrimSuffix(filepath.Base(match), filepath.Ext(match))
|
||||||
|
// If file is called base
|
||||||
|
if fileName == "base" {
|
||||||
|
// Skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
// Parse detected template and base template, add to templates map
|
||||||
|
templates[fileName], err = template.New(
|
||||||
|
filepath.Base(match)).Funcs(
|
||||||
|
sprig.FuncMap()).Funcs(
|
||||||
|
getFuncMap()).ParseFiles(
|
||||||
|
"resources/templates/base.html", match)
|
||||||
|
if err != nil {
|
||||||
|
Log.Fatal().Str("template", fileName).Err(err).Msg("Error parsing template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open sqlite database called sessions.db for storing sessions
|
||||||
|
sessionDB, _ := gorm.Open(sqlite.Open("sessions.db"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||||
|
// Create session store from database
|
||||||
|
sessionStore := gormstore.New(sessionDB, []byte(""))
|
||||||
|
|
||||||
|
// Create channel to stop periodic cleanup
|
||||||
|
quitCleanup := make(chan struct{})
|
||||||
|
// Clean up expired sessions every hour
|
||||||
|
go sessionStore.PeriodicCleanup(1*time.Hour, quitCleanup)
|
||||||
|
|
||||||
|
// Open config file
|
||||||
|
configFile, err := os.Open(filepath.Clean(*config))
|
||||||
|
if err != nil {
|
||||||
|
Log.Fatal().Err(err).Msg("Error opening config file")
|
||||||
|
}
|
||||||
|
// Create new TOML decoder
|
||||||
|
dec := toml.NewDecoder(configFile)
|
||||||
|
// Create new nil variable to store decoded config
|
||||||
|
var decodedConf Conf
|
||||||
|
// Decode config into variable
|
||||||
|
err = dec.Decode(&decodedConf)
|
||||||
|
if err != nil {
|
||||||
|
Log.Fatal().Err(err).Msg("Error decoding config file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register HTTP routes
|
||||||
|
registerRoutes(App{
|
||||||
|
Route: router,
|
||||||
|
Templates: templates,
|
||||||
|
Session: sessionStore,
|
||||||
|
Config: decodedConf,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create string address from flag values
|
||||||
|
strAddr := fmt.Sprint(*addr, ":", *port)
|
||||||
|
// Create listener on IPv4 using address created above
|
||||||
|
ln, err := net.Listen("tcp4", strAddr)
|
||||||
|
if err != nil {
|
||||||
|
Log.Fatal().Err(err).Msg("Error creating listener")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log HTTP server start
|
||||||
|
Log.Info().Str("addr", strAddr).Msg("Starting HTTP server")
|
||||||
|
// Start HTTP server using previously-created router and listener
|
||||||
|
err = http.Serve(ln, router)
|
||||||
|
if err != nil {
|
||||||
|
Log.Fatal().Err(err).Msg("Error while serving")
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,65 @@
|
||||||
|
{{define "head"}}
|
||||||
|
<title>{{.SiteTitle}} - {{.PageTitle}}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/bulma.min.css">
|
||||||
|
<style>
|
||||||
|
::-webkit-scrollbar {border-radius: 24px; width: 8px;}
|
||||||
|
::-webkit-scrollbar-thumb {background: #e5e5e5; border-radius: 10px;}
|
||||||
|
</style>
|
||||||
|
<script async src="/js/iconify.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function toggleNavMenu() {
|
||||||
|
const navMenu = document.getElementById("navMenu");
|
||||||
|
const navbarBurger = document.getElementById("navbarBurger");
|
||||||
|
if (navMenu.classList.contains("is-active")) {
|
||||||
|
navMenu.classList.remove("is-active")
|
||||||
|
navbarBurger.classList.remove("is-active")
|
||||||
|
} else {
|
||||||
|
navMenu.classList.add("is-active")
|
||||||
|
navbarBurger.classList.add("is-active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{if eq .Theme "dark"}}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/darkreader.css">
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "navbar"}}
|
||||||
|
<nav class="navbar" role="navigation" aria-label="main nav" >
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">{{.SiteTitle}}</a>
|
||||||
|
|
||||||
|
<a role="button" class="navbar-burger" onclick="toggleNavMenu()" aria-label="menu" aria-expanded="false" id="navbarBurger">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu" id="navMenu">
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a class="navbar-item {{if eq (print .Page) `home`}}is-active{{end}}" href="/">Home</a>
|
||||||
|
{{if and .User (ne .User "_public_")}}
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<a class="navbar-link">{{.User}}</a>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
<a class="navbar-item" href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else if eq .User "_public_"}}
|
||||||
|
<a class="navbar-item {{if eq (print .Page) `login`}}is-active{{end}}" href="/login">Login</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "icon"}}
|
||||||
|
<span class="iconify icon:{{.}} icon-inline:false"></span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "icon-inline"}}
|
||||||
|
<span class="iconify icon:{{.}}"></span>
|
||||||
|
{{end}}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-header-title">{{.Title}}</p>
|
||||||
|
{{if ne .Icon ""}}
|
||||||
|
<div class="card-header-icon subtitle">
|
||||||
|
{{template "icon" .Icon}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
{{ range $name, $info := .Data }}
|
||||||
|
{{$data := dict "target" ""}}
|
||||||
|
{{- if eq $info.target "sameTab" -}}
|
||||||
|
{{- $_ := set $data "target" "_self" -}}
|
||||||
|
{{- else if eq $info.target "newTab" -}}
|
||||||
|
{{- $_ := set $data "target" "_blank" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $_ := set $data "target" "_self" -}}
|
||||||
|
{{- end -}}
|
||||||
|
<a href="{{$info.url}}" class="button is-fullwidth has-text-left is-justify-content-start" target="{{$data.target}}" style="margin-bottom: 0.5rem; background-color: #f5f5f5">{{$name}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div class="card-header">
|
||||||
|
<a class="card-header-title" href="{{.URL}}">
|
||||||
|
{{if ne .Icon ""}}
|
||||||
|
{{template "icon" .Icon}}
|
||||||
|
{{end}}
|
||||||
|
{{.Title}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{if ne .Description ""}}
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{.Description}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if ne .URL ""}}
|
||||||
|
<div class="card-footer" style="margin-top: auto">
|
||||||
|
<a class="card-footer-item has-text-info" href="{{.URL}}">{{.URL}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<div class="card-header">
|
||||||
|
<a class="card-header-title" href="{{.URL}}">
|
||||||
|
{{if ne .Icon ""}}
|
||||||
|
{{template "icon" .Icon}}
|
||||||
|
{{end}}
|
||||||
|
{{.Title}}
|
||||||
|
</a>
|
||||||
|
<div class="card-header-icon">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<p class="tag">Status</p>
|
||||||
|
<p class="tag is-warning" id="{{.Title}}Status">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if ne .Description ""}}
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{.Description}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="card-footer" style="margin-top: auto">
|
||||||
|
<a class="card-footer-item has-text-info" href="{{.URL}}">{{.URL}}</a>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var request = new XMLHttpRequest()
|
||||||
|
request.open('GET', "/status/{{b64enc .URL}}", true)
|
||||||
|
request.onload = function () {
|
||||||
|
var data = JSON.parse(this.response)
|
||||||
|
if (data.down === true || parseInt(data.code) > 500 && parseInt(data.code) < 600 ) {
|
||||||
|
document.getElementById('{{.Title}}Status').classList.remove("is-warning")
|
||||||
|
document.getElementById('{{.Title}}Status').classList.add("is-danger")
|
||||||
|
document.getElementById('{{.Title}}Status').innerHTML = "Offline"
|
||||||
|
} else {
|
||||||
|
document.getElementById('{{.Title}}Status').classList.remove("is-warning")
|
||||||
|
document.getElementById('{{.Title}}Status').classList.add("is-success")
|
||||||
|
document.getElementById('{{.Title}}Status').innerHTML = "Online"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.send()
|
||||||
|
</script>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-header-title">{{.Title}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<p id="weatherLoadingText">Loading...</p>
|
||||||
|
<div class="columns is-mobile">
|
||||||
|
<div class="column is-half">
|
||||||
|
<object type="image/svg+xml" id="weatherStateImg" style="width:45px; height: 45px"></object>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half">
|
||||||
|
<p id="weatherTempText" class="has-text-right subtitle"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="subtitle is-marginless" id="weatherStateText"></p>
|
||||||
|
<p id="weatherMinText"></p>
|
||||||
|
<p id="weatherMaxText"></p>
|
||||||
|
<p id="weatherWindSpeedText"></p>
|
||||||
|
<p id="weatherHumidityText"></p>
|
||||||
|
<p id="weatherVisibilityText"></p>
|
||||||
|
<p id="weatherPredictabilityText"></p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer" style="margin-top: auto">
|
||||||
|
<span class="card-footer-item">Data from <a href="https://www.metaweather.com" class="has-text-info">Metaweather</a></span>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var request = new XMLHttpRequest()
|
||||||
|
request.open('GET', "{{proxy (printf `https://www.metaweather.com/api/location/%s/` .Data.woeid) }}", true)
|
||||||
|
const round = function (flt){return Number.parseFloat(flt).toPrecision(3)}
|
||||||
|
request.onload = function () {
|
||||||
|
const data = JSON.parse(this.response)
|
||||||
|
document.getElementById('weatherLoadingText').classList.add("is-hidden")
|
||||||
|
document.getElementById('weatherStateText').innerText = data["consolidated_weather"][0]["weather_state_name"]
|
||||||
|
document.getElementById('weatherTempText').innerHTML = round(data["consolidated_weather"][0]["the_temp"]*1.8+32) + " °F"
|
||||||
|
document.getElementById('weatherStateImg').data = "/proxy/" + btoa("https://www.metaweather.com/static/img/weather/" + data["consolidated_weather"][0]["weather_state_abbr"] + ".svg")
|
||||||
|
document.getElementById('weatherMinText').innerHTML = "Min: " + round(data["consolidated_weather"][0]["min_temp"]*1.8+32) + " °F"
|
||||||
|
document.getElementById('weatherMaxText').innerHTML = "Max: " + round(data["consolidated_weather"][0]["max_temp"]*1.8+32) + " °F"
|
||||||
|
document.getElementById('weatherWindSpeedText').innerText = "Wind Speed: " + round(data["consolidated_weather"][0]["wind_speed"]) + "mph"
|
||||||
|
document.getElementById('weatherHumidityText').innerText = "Humidity: " + data["consolidated_weather"][0]["humidity"] + "%"
|
||||||
|
document.getElementById('weatherVisibilityText').innerText = "Visibility: " + round(data["consolidated_weather"][0]["visibility"]) + "mi"
|
||||||
|
document.getElementById('weatherPredictabilityText').innerText = "Predictability: " + data["consolidated_weather"][0]["predictability"] + "%"
|
||||||
|
}
|
||||||
|
request.send()
|
||||||
|
</script>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{{template "head" dict
|
||||||
|
"SiteTitle" .Config.Title
|
||||||
|
"PageTitle" "Error"
|
||||||
|
"Theme" .Config.Theme}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{template "navbar" dict
|
||||||
|
"SiteTitle" .Config.Title
|
||||||
|
"Page" "error"
|
||||||
|
"User" ""}}
|
||||||
|
<div class="hero is-fullheight-with-navbar is-light">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<p class="title">Error <span class="has-text-danger">{{.StatusCode}}</span></p>
|
||||||
|
<p class="subtitle">{{.Reason}}</p>
|
||||||
|
<a class="button is-danger" href="/">Go to homepage</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
{{template "base.html"}}
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{{template "head" dict
|
||||||
|
"SiteTitle" .Config.Title
|
||||||
|
"PageTitle" "Home"
|
||||||
|
"Theme" .Config.Theme}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "navbar" dict
|
||||||
|
"SiteTitle" .Config.Title
|
||||||
|
"Page" "home"
|
||||||
|
"User" .Username }}
|
||||||
|
<div class="hero is-fullheight-with-navbar is-light">
|
||||||
|
<div class="hero-head" style="margin: 10px 30px 0 30px">
|
||||||
|
<div class="row columns is-multiline is-mobile">
|
||||||
|
{{if .User.ShowPublic}}
|
||||||
|
{{ range $_, $card := .Config.Users._public_.Cards }}
|
||||||
|
<div class="column is-half-tablet is-one-quarter-fullhd is-one-third-desktop is-full-mobile">
|
||||||
|
<div class="card is-flex is-flex-direction-column" style="min-height: 175px; max-height: 175px; overflow: auto">
|
||||||
|
{{dyn_template $card.Type $card}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{ range $_, $card := .User.Cards }}
|
||||||
|
<div class="column is-half-tablet is-one-quarter-fullhd is-one-third-desktop is-full-mobile">
|
||||||
|
<div class="card is-flex is-flex-direction-column" style="min-height: 175px; max-height: 175px; overflow: auto">
|
||||||
|
{{dyn_template $card.Type $card}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
{{template "base.html"}}
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{{template "head" dict
|
||||||
|
"SiteTitle" .Config.Title
|
||||||
|
"PageTitle" "Login"
|
||||||
|
"Theme" .Config.Theme}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "navbar" dict "SiteTitle" .Config.Title "Page" "login" "User" .Username}}
|
||||||
|
<div class="hero is-fullheight-with-navbar is-light">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<article style="max-width: 30rem; margin-left: auto; margin-right: auto" class="message is-danger">
|
||||||
|
{{if eq .Error "usr"}}
|
||||||
|
<p class="message-header">User does not exist!</p>
|
||||||
|
{{else if eq .Error "pwd"}}
|
||||||
|
<p class="message-header">Incorrect password!</p>
|
||||||
|
{{end}}
|
||||||
|
</article>
|
||||||
|
<p class="subtitle">Login to {{.Config.Title}}</p>
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<input style="max-width: 20rem" class="input is-info" type="text" placeholder="Username" name="username"><br>
|
||||||
|
<br>
|
||||||
|
<input style="max-width: 20rem" class="input is-info" type="password" placeholder="Password" name="password"><br><br>
|
||||||
|
<input class="button" style="background-color: #f5f5f5" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,220 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create struct to store template context
|
||||||
|
type TemplateData struct {
|
||||||
|
Username string
|
||||||
|
Config Conf
|
||||||
|
User User
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerRoutes(app App) {
|
||||||
|
// Root endpoint, home page
|
||||||
|
app.Route.Path("/").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Get session by name from config
|
||||||
|
session, _ := app.Session.Get(req, app.Config.Session.Name)
|
||||||
|
// Attempt to get loggedInAs from session
|
||||||
|
loggedInAs, ok := session.Values["loggedInAs"].(string)
|
||||||
|
// If user not logged in and login is required
|
||||||
|
if !ok && app.Config.LoginRequired {
|
||||||
|
// Redirect to login page
|
||||||
|
http.Redirect(res, req, "/login", http.StatusFound)
|
||||||
|
} else if !ok && !app.Config.LoginRequired {
|
||||||
|
// If not logged in and login is not required
|
||||||
|
// Set logged in to public user
|
||||||
|
loggedInAs = "_public_"
|
||||||
|
// Set logged in user to session
|
||||||
|
session.Values["loggedInAs"] = loggedInAs
|
||||||
|
// Save session
|
||||||
|
_ = session.Save(req, res)
|
||||||
|
}
|
||||||
|
// Create template context
|
||||||
|
tmplData := TemplateData{Username: loggedInAs, Config: app.Config, User: app.Config.Users[loggedInAs]}
|
||||||
|
// Execute home template with provided context
|
||||||
|
err := app.Templates["home"].Execute(res, tmplData)
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Error executing home template")
|
||||||
|
Log.Warn().Err(err).Msg("Error executing home template")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// /login endpoint, login page
|
||||||
|
app.Route.Path("/login").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Check request method
|
||||||
|
switch req.Method {
|
||||||
|
// If GET request
|
||||||
|
case http.MethodGet:
|
||||||
|
// Get session by name from config
|
||||||
|
session, _ := app.Session.Get(req, app.Config.Session.Name)
|
||||||
|
// Attempt to get loggedInAs from session
|
||||||
|
loggedInAs, ok := session.Values["loggedInAs"].(string)
|
||||||
|
// If logged in and not as public user
|
||||||
|
if ok && loggedInAs != "_public_" {
|
||||||
|
// Redirect back to home page
|
||||||
|
http.Redirect(res, req, "/", http.StatusFound)
|
||||||
|
}
|
||||||
|
// Get query parameter error
|
||||||
|
urlErr := req.URL.Query().Get("error")
|
||||||
|
// Execute login template with provided context
|
||||||
|
err := app.Templates["login"].Execute(res, TemplateData{Config: app.Config, Error: urlErr, Username: loggedInAs})
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Error executing login template")
|
||||||
|
Log.Warn().Err(err).Msg("Error executing login template")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If POST request
|
||||||
|
case http.MethodPost:
|
||||||
|
// Parse form in POST request body
|
||||||
|
_ = req.ParseForm()
|
||||||
|
// Get password from form
|
||||||
|
password := req.PostForm.Get("password")
|
||||||
|
// Get username from form
|
||||||
|
username := req.PostForm.Get("username")
|
||||||
|
// Get user from config by username
|
||||||
|
user, ok := app.Config.Users[username]
|
||||||
|
// If user not found
|
||||||
|
if !ok {
|
||||||
|
// Redirect to login page with error parameter set to usr
|
||||||
|
http.Redirect(res, req, "/login?error=usr", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Compare hash stored in config to password in form
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
||||||
|
// If password was incorrect
|
||||||
|
if err != nil {
|
||||||
|
// Redirect to login page with error parameter set to pwd
|
||||||
|
http.Redirect(res, req, "/login?error=pwd", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get session by name from config
|
||||||
|
session, _ := app.Session.Get(req, app.Config.Session.Name)
|
||||||
|
// Set loggedInAs value in session to username from form
|
||||||
|
session.Values["loggedInAs"] = username
|
||||||
|
// Save session
|
||||||
|
err = session.Save(req, res)
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Error saving session")
|
||||||
|
Log.Warn().Err(err).Msg("Error saving session")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Redirect to homepage
|
||||||
|
http.Redirect(res, req, "/", http.StatusFound)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// /logout endpoint, logout and redirect
|
||||||
|
app.Route.Path("/logout").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Get session by name from config
|
||||||
|
session, _ := app.Session.Get(req, app.Config.Session.Name)
|
||||||
|
// Remove loggedInAs value from session
|
||||||
|
delete(session.Values, "loggedInAs")
|
||||||
|
// Save session
|
||||||
|
err := session.Save(req, res)
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Error saving session")
|
||||||
|
Log.Warn().Err(err).Msg("Error while handling error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Redirect to login page
|
||||||
|
http.Redirect(res, req, "/login", http.StatusFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
// /status/:b64url endpoint, return status of base64 encoded URL as JSON
|
||||||
|
app.Route.Path("/status/{b64url}").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Get path variables
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
// Create JSON encoder writing to response
|
||||||
|
enc := json.NewEncoder(res)
|
||||||
|
// Decode base64 URL string
|
||||||
|
url, _ := base64.StdEncoding.DecodeString(vars["b64url"])
|
||||||
|
// Create new HEAD request to check status without downloading whole page
|
||||||
|
headReq, err := http.NewRequest(http.MethodHead, string(url), nil)
|
||||||
|
if err != nil {
|
||||||
|
// Encode down status and write to response
|
||||||
|
_ = enc.Encode(map[string]interface{}{"code": 0, "down": true})
|
||||||
|
Log.Warn().Err(err).Msg("Error creating HEAD request for status check")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create new HTTP client with 5 second timeout
|
||||||
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
// Use client to do request created above
|
||||||
|
headRes, err := client.Do(headReq)
|
||||||
|
if err != nil {
|
||||||
|
// Encode down status and write to response
|
||||||
|
_ = enc.Encode(map[string]interface{}{"code": 0, "down": true})
|
||||||
|
Log.Warn().Err(err).Msg("Error executing HEAD request for status check")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Encode returned status and write to response
|
||||||
|
_ = enc.Encode(map[string]interface{}{"code": headRes.StatusCode, "down": false})
|
||||||
|
})
|
||||||
|
|
||||||
|
// /proxy/:b64url endpoint, proxy HTTP connection bypassing CORS
|
||||||
|
app.Route.Path("/proxy/{b64url}").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Get path variables
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
// Decode base64 URL string
|
||||||
|
url, _ := base64.StdEncoding.DecodeString(vars["b64url"])
|
||||||
|
// If URL is allowed for proxy
|
||||||
|
if strSlcContains(app.Config.AllowProxy, string(url)) {
|
||||||
|
// Create new HTTP request with the same parameters as sent to endpoint
|
||||||
|
proxyReq, err := http.NewRequest(req.Method, string(url), req.Body)
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Proxying connection failed")
|
||||||
|
Log.Warn().Err(err).Msg("Error creating request for proxy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create new HTTP client with 5 second timeout
|
||||||
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
// Use client to do request created above
|
||||||
|
proxyRes, err := client.Do(proxyReq)
|
||||||
|
if err != nil {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusInternalServerError, "Proxying connection failed")
|
||||||
|
Log.Warn().Err(err).Msg("Error executing request for proxy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Close proxy response body at end of function
|
||||||
|
defer proxyRes.Body.Close()
|
||||||
|
// Copy data from proxy response to response
|
||||||
|
io.Copy(res, proxyRes.Body)
|
||||||
|
} else {
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusBadRequest, "This url is not in allowedProxy")
|
||||||
|
Log.Warn().Str("url", string(url)).Msg("URL is disallowed for proxy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Catch-all route, gzip-compressing file server
|
||||||
|
app.Route.PathPrefix("/").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
// Create OS-specific path to requested file
|
||||||
|
filePath := filepath.Join("resources", "public", req.URL.Path)
|
||||||
|
// Open requested file
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
Log.Warn().Str("file", filePath).Msg("File not found")
|
||||||
|
httpError(res, app.Templates["error"], app.Config, http.StatusNotFound, "This file was not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Close file at end of function
|
||||||
|
defer file.Close()
|
||||||
|
// Compress response
|
||||||
|
gzRes := compressRes(res)
|
||||||
|
// Close compressed response at end of function
|
||||||
|
defer gzRes.Close()
|
||||||
|
// Copy file contents to compressed response
|
||||||
|
io.Copy(gzRes, file)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
title = "SimpleDash"
|
||||||
|
theme = "dark"
|
||||||
|
loginRequired = false
|
||||||
|
allowProxy = ["https://www.metaweather.com/"]
|
||||||
|
|
||||||
|
[session]
|
||||||
|
name = "simpledash-session"
|
||||||
|
|
||||||
|
[users]
|
||||||
|
[[users._public_.card]]
|
||||||
|
type = "weather"
|
||||||
|
title = "Weather"
|
||||||
|
data = {"woeid" = "2442047"}
|
||||||
|
|
||||||
|
[users.admin]
|
||||||
|
passwordHash = "$2a$10$w00dzQ1PP6nwXLhuzV2pFOUU6m8bcZXtDX3UVxpOYq3fTSwVMqPge"
|
||||||
|
showPublic = true
|
||||||
|
|
||||||
|
[[users.admin.card]]
|
||||||
|
type = "status"
|
||||||
|
title = "Google"
|
||||||
|
icon = "ion:logo-google"
|
||||||
|
desc = "Google search engine. Status card example."
|
||||||
|
url = "https://www.google.com"
|
||||||
|
|
||||||
|
[[users.admin.card]]
|
||||||
|
type = "simple"
|
||||||
|
title = "Gmail"
|
||||||
|
icon = "simple-icons:gmail"
|
||||||
|
desc = "Gmail mail client. Simple card example"
|
||||||
|
url = "http://openwrt/"
|
||||||
|
|
||||||
|
[[users.admin.card]]
|
||||||
|
type = "collection"
|
||||||
|
title = "Programming"
|
||||||
|
icon = "entypo:code"
|
||||||
|
[users.admin.card.data]
|
||||||
|
Godoc = {"url" = "https://pkg.go.dev", "target" = "newTab"}
|
||||||
|
Ruby-Doc = {"url" = "https://ruby-doc.org/", "target" = "sameTab"}
|
||||||
|
|
||||||
|
[[users.admin.card]]
|
||||||
|
type = "collection"
|
||||||
|
title = "Science"
|
||||||
|
icon = "ic:outline-science"
|
||||||
|
data = {"Google Scholar" = {"url" = "https://robinhood.com/", "target" = "sameTab"}}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Function to dynamically execute template and return results
|
||||||
|
func dynamicTemplate(name string, data interface{}) (template.HTML, error) {
|
||||||
|
// Create new buffer
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
// Execute template writing to buffer with provided data
|
||||||
|
err := templates[name].Execute(buf, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
// Return results of template execution
|
||||||
|
return template.HTML(buf.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap URL with proxy
|
||||||
|
func wrapProxy(url string) string {
|
||||||
|
// Encode URL with base64
|
||||||
|
b64url := base64.StdEncoding.EncodeToString([]byte(url))
|
||||||
|
// Return /proxy/{url}
|
||||||
|
return fmt.Sprint("/proxy/", b64url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get template function map
|
||||||
|
func getFuncMap() template.FuncMap {
|
||||||
|
// Return function map with template functions
|
||||||
|
return template.FuncMap{
|
||||||
|
"dyn_template": dynamicTemplate,
|
||||||
|
"proxy": wrapProxy,
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue