warning: {{ index .Flashes 0 }}
+diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 97f2a12..7ec4b2d 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -194,11 +194,14 @@ func main() { SELECT 'albums', 500000 WHERE NOT EXISTS (SELECT * FROM sqlite_sequence) `) - orm.Exec(` - INSERT INTO users(username, password) - SELECT 'admin', 'admin' - WHERE NOT EXISTS (SELECT * FROM users) - `) + orm.FirstOrCreate(&db.User{}, db.User{ + Name: "admin", + Password: "admin", + }) + orm.FirstOrCreate(&db.User{}, db.User{ + Name: "senan", + Password: "password", + }) startTime := time.Now() tx = orm.Begin() err := godirwalk.Walk(os.Args[1], &godirwalk.Options{ diff --git a/cmd/server/main.go b/cmd/server/main.go index 80d02b3..e74734a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -9,6 +9,11 @@ import ( "github.com/sentriz/gonic/handler" _ "github.com/jinzhu/gorm/dialects/sqlite" + "github.com/wader/gormstore" +) + +var ( + dbCon = db.New() ) type middleware func(next http.HandlerFunc) http.HandlerFunc @@ -25,17 +30,15 @@ func newChain(wares ...middleware) middleware { } } -func main() { - address := ":5000" +func setSubsonicRoutes(mux *http.ServeMux) { cont := handler.Controller{ - DB: db.New(), + DB: dbCon, } withWare := newChain( cont.LogConnection, cont.EnableCORS, cont.CheckParameters, ) - mux := http.NewServeMux() mux.HandleFunc("/rest/ping", withWare(cont.Ping)) mux.HandleFunc("/rest/ping.view", withWare(cont.Ping)) mux.HandleFunc("/rest/stream", withWare(cont.Stream)) @@ -56,11 +59,31 @@ func main() { mux.HandleFunc("/rest/getAlbumList2.view", withWare(cont.GetAlbumList)) mux.HandleFunc("/rest/getLicense", withWare(cont.GetLicence)) mux.HandleFunc("/rest/getLicense.view", withWare(cont.GetLicence)) - mux.HandleFunc("/", withWare(cont.NotFound)) - // mux.HandleFunc("/rest/getMusicDirectory", withWare(cont.GetMusicDirectory)) - // mux.HandleFunc("/rest/getMusicDirectory.view", withWare(cont.GetMusicDirectory)) - // mux.HandleFunc("/rest/getIndexes", withWare(cont.GetIndexes)) - // mux.HandleFunc("/rest/getIndexes.view", withWare(cont.GetIndexes)) +} + +func setAdminRoutes(mux *http.ServeMux) { + cont := handler.Controller{ + DB: dbCon, + SStore: gormstore.New(dbCon, []byte("saynothinboys")), + } + withWare := newChain( + cont.LogConnection, + cont.EnableCORS, + ) + server := http.FileServer(http.Dir("static")) + mux.Handle("/admin/static/", http.StripPrefix("/admin/static/", server)) + mux.HandleFunc("/admin/login", withWare(cont.ServeLogin)) + mux.HandleFunc("/admin/authenticate", withWare(cont.ServeAuthenticate)) + mux.HandleFunc("/admin/home", withWare(cont.ServeHome)) + mux.HandleFunc("/admin/create_user", withWare(cont.ServeCreateUser)) + mux.HandleFunc("/admin/logout", withWare(cont.ServeLogout)) +} + +func main() { + address := ":5000" + mux := http.NewServeMux() + setSubsonicRoutes(mux) + setAdminRoutes(mux) server := &http.Server{ Addr: address, Handler: mux, diff --git a/db/model.go b/db/model.go index 1685556..b812ca8 100644 --- a/db/model.go +++ b/db/model.go @@ -50,7 +50,7 @@ type Cover struct { // User represents the users table type User struct { - IDBase - Username string `gorm:"not null;unique_index"` + Base + Name string `gorm:"not null;unique_index"` Password string } diff --git a/go.mod b/go.mod index 9424096..105acfb 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect + github.com/gorilla/sessions v1.1.3 github.com/jinzhu/gorm v1.9.2 github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect github.com/jinzhu/now v1.0.0 // indirect @@ -16,6 +17,7 @@ require ( github.com/mozillazg/go-unidecode v0.1.1 github.com/myesui/uuid v1.0.0 // indirect github.com/twinj/uuid v1.0.0 + github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect google.golang.org/appengine v1.5.0 // indirect ) diff --git a/go.sum b/go.sum index bc69b42..48cae2e 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,12 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +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/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.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= @@ -100,6 +106,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= +github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755 h1:pNaEDfvqe9W2h4D+xm5f+lnZdao3Rob6O0b8SovpGbE= +github.com/wader/gormstore v0.0.0-20190302154359-acb787ba3755/go.mod h1:PbEnTGtqU8NGCALR62gu2+eQYO8zQDEvaMJiPaj5Hic= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= diff --git a/handler/admin.go b/handler/admin.go new file mode 100644 index 0000000..8cb8774 --- /dev/null +++ b/handler/admin.go @@ -0,0 +1,77 @@ +package handler + +import ( + "net/http" + + "github.com/sentriz/gonic/db" +) + +func (c *Controller) ServeLogin(w http.ResponseWriter, r *http.Request) { + session, _ := c.SStore.Get(r, "gonic") + renderTemplate(w, r, session, "login", &templateData{}) +} + +func (c *Controller) ServeAuthenticate(w http.ResponseWriter, r *http.Request) { + session, _ := c.SStore.Get(r, "gonic") + username := r.FormValue("username") + password := r.FormValue("password") + if username == "" || password == "" { + session.AddFlash("please provide both a username and password") + session.Save(r, w) + http.Redirect(w, r, "/admin/login", 303) + return + } + var user db.User + c.DB.Where("name = ?", username).First(&user) + if !(username == user.Name && password == user.Password) { + session.AddFlash("invalid username / password") + session.Save(r, w) + http.Redirect(w, r, "/admin/login", 303) + return + } + session.Values["authenticated"] = true + session.Values["user"] = user.ID + session.Save(r, w) + http.Redirect(w, r, "/admin/home", 303) +} + +func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) { + session, _ := c.SStore.Get(r, "gonic") + authed, _ := session.Values["authenticated"].(bool) + if !authed { + session.AddFlash("you are not authenticated") + session.Save(r, w) + http.Redirect(w, r, "/admin/login", 303) + return + } + var data templateData + var user db.User + c.DB.First(&user, session.Values["user"]) + data.UserID = user.ID + data.Username = user.Name + c.DB.Table("album_artists").Count(&data.ArtistCount) + c.DB.Table("albums").Count(&data.AlbumCount) + c.DB.Table("tracks").Count(&data.TrackCount) + c.DB.Find(&data.Users) + renderTemplate(w, r, session, "home", &data) +} + +func (c *Controller) ServeCreateUser(w http.ResponseWriter, r *http.Request) { + session, _ := c.SStore.Get(r, "gonic") + authed, _ := session.Values["authenticated"].(bool) + if !authed { + session.AddFlash("you are not authenticated") + session.Save(r, w) + http.Redirect(w, r, "/admin/login", 303) + return + } + renderTemplate(w, r, session, "create_user", &templateData{}) +} + +func (c *Controller) ServeLogout(w http.ResponseWriter, r *http.Request) { + session, _ := c.SStore.Get(r, "gonic") + delete(session.Values, "authenticated") + delete(session.Values, "user") + session.Save(r, w) + http.Redirect(w, r, "/admin/login", 303) +} diff --git a/handler/handler.go b/handler/handler.go index 1be58ff..23dc730 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -4,16 +4,55 @@ import ( "encoding/json" "encoding/xml" "fmt" + "html/template" "log" "net/http" + "path/filepath" "strconv" - "github.com/jinzhu/gorm" + "github.com/gorilla/sessions" + "github.com/sentriz/gonic/db" "github.com/sentriz/gonic/subsonic" + + "github.com/jinzhu/gorm" + "github.com/wader/gormstore" ) +var ( + templates = make(map[string]*template.Template) +) + +func init() { + templates["login"] = template.Must(template.ParseFiles( + filepath.Join("templates", "layout.tmpl"), + filepath.Join("templates", "admin", "login.tmpl"), + )) + templates["home"] = template.Must(template.ParseFiles( + filepath.Join("templates", "layout.tmpl"), + filepath.Join("templates", "user.tmpl"), + filepath.Join("templates", "admin", "home.tmpl"), + )) + templates["create_user"] = template.Must(template.ParseFiles( + filepath.Join("templates", "layout.tmpl"), + filepath.Join("templates", "user.tmpl"), + filepath.Join("templates", "admin", "create_user.tmpl"), + )) +} + type Controller struct { - DB *gorm.DB + DB *gorm.DB + SStore *gormstore.Store + Templates map[string]*template.Template +} + +type templateData struct { + Flashes []interface{} + UserID uint + Username string + ArtistCount uint + AlbumCount uint + TrackCount uint + Users []*db.User } func getStrParam(r *http.Request, key string) string { @@ -40,7 +79,8 @@ func getIntParamOr(r *http.Request, key string, or int) int { return val } -func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.Response) { +func respondRaw(w http.ResponseWriter, r *http.Request, + code int, sub *subsonic.Response) { res := subsonic.MetaResponse{ Response: sub, } @@ -73,12 +113,26 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic. } } -func respond(w http.ResponseWriter, r *http.Request, sub *subsonic.Response) { +func respond(w http.ResponseWriter, r *http.Request, + sub *subsonic.Response) { respondRaw(w, r, http.StatusOK, sub) } -func respondError(w http.ResponseWriter, r *http.Request, code uint64, message string) { +func respondError(w http.ResponseWriter, r *http.Request, + code uint64, message string) { respondRaw(w, r, http.StatusBadRequest, subsonic.NewError( code, message, )) } + +func renderTemplate(w http.ResponseWriter, r *http.Request, + s *sessions.Session, name string, data *templateData) { + flashes := s.Flashes() + s.Save(r, w) + data.Flashes = flashes + err := templates[name].ExecuteTemplate(w, "layout", data) + if err != nil { + http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500) + return + } +} diff --git a/handler/middleware.go b/handler/middleware.go index 1982838..57b801a 100644 --- a/handler/middleware.go +++ b/handler/middleware.go @@ -62,7 +62,7 @@ func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc { return } user := db.User{ - Username: username, + Name: username, } err := c.DB.Where(user).First(&user).Error if gorm.IsRecordNotFoundError(err) { diff --git a/static/images/gonic.png b/static/images/gonic.png new file mode 100644 index 0000000..0acadc2 Binary files /dev/null and b/static/images/gonic.png differ diff --git a/static/stylesheets/awsm.css b/static/stylesheets/awsm.css new file mode 100644 index 0000000..836c8ab --- /dev/null +++ b/static/stylesheets/awsm.css @@ -0,0 +1,7 @@ +@charset "UTF-8"; +/*! + * awsm.css v3.0.0 (https://igoradamenko.github.io/awsm.css/) + * Copyright 2015 Igor Adamenko + * Licensed under MIT (https://github.com/igoradamenko/awsm.css/blob/master/LICENSE.md) + */ +html{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"PT Sans","Open Sans","Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:100%;line-height:1.4;background:#fff;color:#000;-webkit-overflow-scrolling:touch}body{margin:1.2em;font-size:1rem}@media (min-width:20rem){body{font-size:calc(1rem + .00625*(100vw - 20rem))}}@media (min-width:40rem){body{font-size:1.125rem}}body article,body footer,body header,body main{position:relative;max-width:40rem;margin:0 auto}body>header{margin-bottom:3.5em}body>header h1{margin:0;font-size:1.5em}body>header p{margin:0;font-size:.85em}body>footer{margin-top:4.5em;padding-bottom:1.5em;text-align:center;font-size:.8rem;color:#aaa}details,nav,p{margin:1em 0}nav ul{list-style:none;margin:0;padding:0}nav li{display:inline-block;margin-right:1em;margin-bottom:.25em}a,nav a:visited{color:#0064c1}article header h1 a:visited:hover,article header h2 a:visited:hover,nav a:hover{color:#f00000}ol,ul{margin-top:0;padding-top:0;padding-left:2.5em}article header h1+p,article header h2+p,ol li+li,ul li+li{margin-top:.25em}p{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}aside:first-child,form legend:first-child+label,p:first-child{margin-top:0}aside:last-child,p:last-child{margin-bottom:0}p+ol,p+ul{margin-top:-.75em}p img,p picture{float:right;margin-bottom:.5em;margin-left:.5em}p picture img{float:none;margin:0}blockquote,dd{padding-left:2.5em}dd{margin-bottom:1em;margin-left:0}dt{font-weight:700}blockquote{margin:0}aside{margin:.5em 0;font-style:italic;color:#aaa}@media (min-width:65rem){aside{position:absolute;right:-12.5rem;width:9.375rem;max-width:9.375rem;margin:0;padding-left:.5em;font-size:.8em;border-left:1px solid #f2f2f2}}section+section{margin-top:2em}h1,h2,h3,h4,h5,h6{margin:1.25em 0 0;line-height:1.2}h1:focus>a[href^="#"][id]:empty,h1:hover>a[href^="#"][id]:empty,h2:focus>a[href^="#"][id]:empty,h2:hover>a[href^="#"][id]:empty,h3:focus>a[href^="#"][id]:empty,h3:hover>a[href^="#"][id]:empty,h4:focus>a[href^="#"][id]:empty,h4:hover>a[href^="#"][id]:empty,h5:focus>a[href^="#"][id]:empty,h5:hover>a[href^="#"][id]:empty,h6:focus>a[href^="#"][id]:empty,h6:hover>a[href^="#"][id]:empty{opacity:1}figure+p,h1+details,h1+p,h2+details,h2+p,h3+details,h3+p,h4+details,h4+p,h5+details,h5+p,h6+details,h6+p{margin-top:.5em}h1>a[href^="#"][id]:empty,h2>a[href^="#"][id]:empty,h3>a[href^="#"][id]:empty,h4>a[href^="#"][id]:empty,h5>a[href^="#"][id]:empty,h6>a[href^="#"][id]:empty{position:absolute;left:-.65em;opacity:0;text-decoration:none;font-weight:400;line-height:1;color:#aaa}@media (min-width:40rem){h1>a[href^="#"][id]:empty,h2>a[href^="#"][id]:empty,h3>a[href^="#"][id]:empty,h4>a[href^="#"][id]:empty,h5>a[href^="#"][id]:empty,h6>a[href^="#"][id]:empty{left:-.8em}}h1>a[href^="#"][id]:empty:focus,h1>a[href^="#"][id]:empty:hover,h1>a[href^="#"][id]:empty:target,h2>a[href^="#"][id]:empty:focus,h2>a[href^="#"][id]:empty:hover,h2>a[href^="#"][id]:empty:target,h3>a[href^="#"][id]:empty:focus,h3>a[href^="#"][id]:empty:hover,h3>a[href^="#"][id]:empty:target,h4>a[href^="#"][id]:empty:focus,h4>a[href^="#"][id]:empty:hover,h4>a[href^="#"][id]:empty:target,h5>a[href^="#"][id]:empty:focus,h5>a[href^="#"][id]:empty:hover,h5>a[href^="#"][id]:empty:target,h6>a[href^="#"][id]:empty:focus,h6>a[href^="#"][id]:empty:hover,h6>a[href^="#"][id]:empty:target{opacity:1;box-shadow:none;color:#000}h1>a[href^="#"][id]:empty:target:focus,h2>a[href^="#"][id]:empty:target:focus,h3>a[href^="#"][id]:empty:target:focus,h4>a[href^="#"][id]:empty:target:focus,h5>a[href^="#"][id]:empty:target:focus,h6>a[href^="#"][id]:empty:target:focus{outline:0}h1>a[href^="#"][id]:empty::before,h2>a[href^="#"][id]:empty::before,h3>a[href^="#"][id]:empty::before,h4>a[href^="#"][id]:empty::before,h5>a[href^="#"][id]:empty::before,h6>a[href^="#"][id]:empty::before{content:"ยงย "}h1{font-size:2.5em}h2{font-size:1.75em}h3{font-size:1.25em}h4{font-size:1.15em}a abbr,h5,h6{font-size:1em}h6{margin-top:1em}article+article{margin-top:5em}article header p{font-size:.6em;color:#aaa}article header p+h1,article header p+h2{margin-top:-.25em}article header h1 a,article header h2 a{color:#000}article header h1 a:visited,article header h2 a:visited,h6,legend{color:#aaa}article>footer{margin-top:1.5em;font-size:.85em}a:visited{color:#8d39d0}a:active,a:hover{outline-width:0}a:hover{color:#f00000}abbr{margin-right:-.075em;text-decoration:none;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;letter-spacing:.075em;font-size:.9em}img,picture{display:block;max-width:100%;margin:0 auto}audio,video{width:100%;max-width:100%}figure{margin:1em 0 .5em;padding:0}figure figcaption{opacity:.65;font-size:.85em}table{display:inline-block;border-spacing:0;border-collapse:collapse;overflow-x:auto;max-width:100%;text-align:left;vertical-align:top;background:linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 0 0,linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 100% 0;background-attachment:scroll,scroll;background-size:1px 100%,1px 100%;background-repeat:no-repeat,no-repeat}table caption{font-size:.9em;background:#fff}table td,table th{padding:.35em .75em;vertical-align:top;font-size:.9em;border:1px solid #f2f2f2;border-top:0;border-left:0}table td:first-child,table th:first-child{padding-left:0;background-image:linear-gradient(to right,#fff 50%,rgba(255,255,255,0) 100%);background-size:2px 100%;background-repeat:no-repeat}table td:last-child,table th:last-child{padding-right:0;border-right:0;background-image:linear-gradient(to left,#fff 50%,rgba(255,255,255,0) 100%);background-position:100% 0;background-size:2px 100%;background-repeat:no-repeat}table td:only-child,table th:only-child{background-image:linear-gradient(to right,#fff 50%,rgba(255,255,255,0) 100%),linear-gradient(to left,#fff 50%,rgba(255,255,255,0) 100%);background-position:0 0,100% 0;background-size:2px 100%,2px 100%;background-repeat:no-repeat,no-repeat}table th{line-height:1.2}form{margin-right:auto;margin-left:auto}@media (min-width:40rem){form{max-width:80%}}form label,form select,output{display:block}form label:not(:first-child){margin-top:1em}form p label{display:inline}form p label+label{margin-left:1em}form input[type],form select,form textarea{margin-bottom:1em}form input[type=checkbox],form input[type=radio]{margin-bottom:0}button,fieldset{margin:0;border:1px solid #aaa}fieldset{padding:.5em 1em}button{outline:0;box-sizing:border-box;height:2em;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border-radius:2px;background:#fff;display:inline-block;width:auto;background:#f2f2f2;color:#000;cursor:pointer}button:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,input[type^=date]:focus,select:focus{border:1px solid #000}button:hover,select:hover{border:1px solid #000}button:active,select:active{background-color:#aaa}select{outline:0;box-sizing:border-box;height:2em;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border:1px solid #aaa;border-radius:2px;background:#fff;display:inline-block;width:auto;background:#f2f2f2;color:#000;cursor:pointer;padding-right:1.2em;background-position:top 55% right .35em;background-size:.5em;background-repeat:no-repeat;-webkit-appearance:button;-moz-appearance:button;appearance:button;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(170, 170, 170)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E")}select:focus,select:hover{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(0, 0, 0)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E")}input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],input[type^=date]{outline:0;box-sizing:border-box;height:2em;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border:1px solid #aaa;border-radius:2px;background:#fff;color:#000;display:block;width:100%;line-height:calc(2em - 1px*2 - (.25em - 1px)*2);-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=email]::-moz-placeholder,input[type=month]::-moz-placeholder,input[type=number]::-moz-placeholder,input[type=password]::-moz-placeholder,input[type=search]::-moz-placeholder,input[type=tel]::-moz-placeholder,input[type=text]::-moz-placeholder,input[type=time]::-moz-placeholder,input[type=url]::-moz-placeholder,input[type=week]::-moz-placeholder,input[type^=date]::-moz-placeholder{color:#aaa}input[type=email]::-webkit-input-placeholder,input[type=month]::-webkit-input-placeholder,input[type=number]::-webkit-input-placeholder,input[type=password]::-webkit-input-placeholder,input[type=search]::-webkit-input-placeholder,input[type=tel]::-webkit-input-placeholder,input[type=text]::-webkit-input-placeholder,input[type=time]::-webkit-input-placeholder,input[type=url]::-webkit-input-placeholder,input[type=week]::-webkit-input-placeholder,input[type^=date]::-webkit-input-placeholder{color:#aaa}input[type=email]:-ms-input-placeholder,input[type=month]:-ms-input-placeholder,input[type=number]:-ms-input-placeholder,input[type=password]:-ms-input-placeholder,input[type=search]:-ms-input-placeholder,input[type=tel]:-ms-input-placeholder,input[type=text]:-ms-input-placeholder,input[type=time]:-ms-input-placeholder,input[type=url]:-ms-input-placeholder,input[type=week]:-ms-input-placeholder,input[type^=date]:-ms-input-placeholder{color:#aaa}input[type=button],input[type=reset],input[type=submit]{outline:0;box-sizing:border-box;height:2em;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border:1px solid #aaa;border-radius:2px;background:#fff;display:inline-block;width:auto;background:#f2f2f2;color:#000;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=button]:focus,input[type=reset]:focus,input[type=submit]:focus{border:1px solid #000}input[type=button]:hover,input[type=reset]:hover,input[type=submit]:hover{border:1px solid #000}input[type=button]:active,input[type=reset]:active,input[type=submit]:active{background-color:#aaa}input[type=color]{outline:0;box-sizing:border-box;height:2em;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border:1px solid #aaa;border-radius:2px;background:#fff;color:#000;display:block;line-height:calc(2em - 1px*2 - (.25em - 1px)*2);-webkit-appearance:none;-moz-appearance:none;appearance:none;width:6em}input[type=color]:focus{border:1px solid #000}input[type=color]::-moz-placeholder,textarea::-moz-placeholder{color:#aaa}input[type=color]::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#aaa}input[type=color]:-ms-input-placeholder{color:#aaa}input[type=color]:hover{border:1px solid #000}input[type=file]{outline:0;box-sizing:border-box;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;border:1px solid #aaa;border-radius:2px;background:#fff;background:#f2f2f2;color:#000;cursor:pointer;display:block;width:100%;height:auto;padding:.75em .5em;font-size:12px;line-height:1}input[type=file]:focus,textarea:focus{border:1px solid #000}input[type=file]:hover{border:1px solid #000}input[type=file]:active{background-color:#aaa}input[type=checkbox],input[type=radio]{margin:-.2em .75em 0 0;vertical-align:middle}textarea{outline:0;box-sizing:border-box;margin:0;padding:calc(.25em - 1px) .5em;font-family:inherit;font-size:1em;border:1px solid #aaa;border-radius:2px;background:#fff;color:#000;display:block;width:100%;line-height:calc(2em - 1px*2 - (.25em - 1px)*2);-webkit-appearance:none;-moz-appearance:none;appearance:none;height:4.5em;resize:vertical;padding-top:.5em;padding-bottom:.5em}textarea:-ms-input-placeholder{color:#aaa}code,kbd,samp,var{font-family:Consolas,"Lucida Console",Monaco,monospace;font-style:normal}pre{overflow-x:auto;font-size:.8em;background:linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 0 0,linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 100% 0;background-attachment:scroll,scroll;background-size:1px 100%,1px 100%;background-repeat:no-repeat,no-repeat}pre>code,summary{display:inline-block}pre>code{overflow-x:visible;box-sizing:border-box;min-width:100%;border-right:3px solid #fff;border-left:1px solid #fff}hr{height:1px;margin:2em 0;border:0;background:#f2f2f2}details[open]{padding-bottom:.5em;border-bottom:1px solid #f2f2f2}summary{font-weight:700;border-bottom:1px dashed;cursor:pointer}summary::-webkit-details-marker{display:none}noscript{color:#d00000}::selection{background:rgba(0,100,193,.25)} \ No newline at end of file diff --git a/static/stylesheets/main.css b/static/stylesheets/main.css new file mode 100644 index 0000000..a946a34 --- /dev/null +++ b/static/stylesheets/main.css @@ -0,0 +1,77 @@ +div { + margin: 0; + padding: 0; +} + +body { + max-width: 800px; + margin: 0 auto; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +form { + max-width: unset; +} + +#content > * { + margin: 1rem 0; +} + +.right { + text-align: right; +} + +.light { + color: #00000082; +} + +.mono { + font-family: monospace; +} + +.pre { + white-space: pre; +} + +.box { + background-color: #0000000a; +} + +.padded { + padding: 1rem; +} + +#header { + /* background-color: #fdc71b08; */ + background-image: linear-gradient(white, #fdad1b0d); + border-bottom: 1px solid; + padding-top: 3rem; +} + +#header img { + max-width: 580px; + position: relative; +} + +#footer { + background-color: #fdad1b0d; + border-top: 1px solid; + text-align: right; +} + +#flashes { + background-color: #fd1b1b29; +} + +#login-form { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +#login-form input[type=submit] { + width: 8rem; +} diff --git a/static/stylesheets/tacit.css b/static/stylesheets/tacit.css new file mode 100644 index 0000000..f262535 --- /dev/null +++ b/static/stylesheets/tacit.css @@ -0,0 +1,3 @@ +input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}th{font-weight:600}td,th{border-bottom:1.08px solid #595959;padding:14.85px 18px;text-align:left;vertical-align:top}thead th{border-bottom-width:2.16px;padding-bottom:6.3px}table{display:table;width:100%}@media all and (max-width: 1024px){table{display:none}}@media all and (max-width: 1024px){table thead{display:none}}table tr{border-bottom-width:2.16px}table tr th{border-bottom-width:2.16px}table tr td,table tr th{overflow:hidden;padding:5.4px 3.6px}@media all and (max-width: 1024px){table tr td,table tr th{border:0;display:inline-block}}@media all and (max-width: 1024px){table tr{display:inline-block;margin:10.8px 0}}@media all and (max-width: 1024px){table{display:inline-block}}input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}fieldset{display:flex;flex-direction:row;flex-wrap:wrap}fieldset legend{margin:18px 0}input,textarea,select,button{border-radius:3.6px;display:inline-block;padding:9.9px}input+label,input+input[type="checkbox"],input+input[type="radio"],textarea+label,textarea+input[type="checkbox"],textarea+input[type="radio"],select+label,select+input[type="checkbox"],select+input[type="radio"],button+label,button+input[type="checkbox"],button+input[type="radio"]{page-break-before:always}input,select,label{margin-right:3.6px}textarea{min-height:90px;min-width:360px}label{display:inline-block;margin-bottom:12.6px}label+*{page-break-before:always}label>input{margin-bottom:0}input[type="submit"],input[type="reset"],button{background:#f2f2f2;color:#191919;cursor:pointer;display:inline;margin-bottom:18px;margin-right:7.2px;padding:6.525px 23.4px;text-align:center}input[type="submit"]:hover,input[type="reset"]:hover,button:hover{background:#d9d9d9;color:#000}input[type="submit"][disabled],input[type="reset"][disabled],button[disabled]{background:#e6e5e5;color:#403f3f;cursor:not-allowed}input[type="submit"],button[type="submit"]{background:#275a90;color:#fff}input[type="submit"]:hover,button[type="submit"]:hover{background:#173454;color:#bfbfbf}input,select,textarea{margin-bottom:18px}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="phone"],input[type="tel"],input[type="number"],input[type="datetime"],input[type="date"],input[type="month"],input[type="week"],input[type="color"],input[type="time"],input[type="search"],input[type="range"],input[type="file"],input[type="datetime-local"],select,textarea{border:1px solid #595959;padding:5.4px 6.3px}input[type="checkbox"],input[type="radio"]{flex-grow:0;height:29.7px;margin-left:0;margin-right:9px;vertical-align:middle}input[type="checkbox"]+label,input[type="radio"]+label{page-break-before:avoid}select[multiple]{min-width:270px}input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}pre,code,kbd,samp,var,output{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14.4px}pre{border-left:1.8px solid #59c072;line-height:25.2px;overflow:auto;padding-left:18px}pre code{background:none;border:0;line-height:29.7px;padding:0}code,kbd{background:#daf1e0;border-radius:3.6px;color:#2a6f3b;display:inline-block;line-height:18px;padding:3.6px 6.3px 2.7px}kbd{background:#2a6f3b;color:#fff}mark{background:#ffc;padding:0 3.6px}input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}h1,h2,h3,h4,h5,h6{color:#000;margin-bottom:18px}h1{font-size:36px;font-weight:500;line-height:41.4px;margin-top:72px}h2{font-size:25.2px;font-weight:400;line-height:30.6px;margin-top:54px}h3{font-size:21.6px;line-height:27px;margin-top:36px}h4{font-size:18px;line-height:23.4px;margin-top:18px}h5{font-size:14.4px;font-weight:bold;line-height:21.6px;text-transform:uppercase}h6{color:#595959;font-size:14.4px;font-weight:bold;line-height:18px;text-transform:uppercase}input,textarea,select,button,option,html,body{font-family:system-ui,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:18px;font-stretch:normal;font-style:normal;font-weight:400;line-height:29.7px}a{color:#275a90;text-decoration:none}a:hover{text-decoration:underline}hr{border-bottom:1px solid #595959}figcaption,small{font-size:15.3px}figcaption{color:#595959}var,em,i{font-style:italic}dt,strong,b{font-weight:600}del,s{text-decoration:line-through}ins,u{text-decoration:underline}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}*{border:0;border-collapse:separate;border-spacing:0;box-sizing:border-box;margin:0;max-width:100%;padding:0;vertical-align:baseline}html,body{width:100%}html{height:100%}body{background:#f5f5f5;color:#1a1919;padding:36px}p,ul,ol,dl,blockquote,hr,pre,table,form,fieldset,figure,address{margin-bottom:29.7px}section{margin-left:auto;margin-right:auto;width:900px}article,header,footer{padding:43.2px}article{background:#fff;border:1px solid #d9d9d9;border-radius:7.2px}nav{text-align:center}nav ul{list-style:none;margin-left:0;text-align:center}nav ul li{display:inline-block;margin-left:9px;margin-right:9px;vertical-align:middle}nav ul li:first-child{margin-left:0}nav ul li:last-child{margin-right:0}ol,ul{margin-left:31.5px}li dl,li ol,li ul{margin-bottom:0}dl{display:inline-block}dt{padding:0 18px}dd{padding:0 18px 4.5px}dd:last-of-type{border-bottom:1.08px solid #595959}dd+dt{border-top:1.08px solid #595959;padding-top:9px}blockquote{border-left:2.16px solid #595959;padding:4.5px 18px 4.5px 15.84px}blockquote footer{color:#595959;font-size:13.5px;margin:0}blockquote p{margin-bottom:0}img{height:auto;margin:0 auto}figure img{display:block}@media (max-width: 767px){body{padding:18px 0}article{border:0;padding:18px}header,footer{padding:18px}textarea,input,select{min-width:0}fieldset{min-width:0}fieldset *{flex-grow:1;page-break-before:auto}section{width:auto}x:-moz-any-link{display:table-cell}} + +/*# sourceMappingURL=tacit-css-1.4.2.min.css.map */ \ No newline at end of file diff --git a/templates/admin/create_user.tmpl b/templates/admin/create_user.tmpl new file mode 100644 index 0000000..8afd6ff --- /dev/null +++ b/templates/admin/create_user.tmpl @@ -0,0 +1,12 @@ +{{ define "title" }}home{{ end }} + +{{ define "user" }} +
+ warning: {{ index .Flashes 0 }}
+