Initial commit

This commit is contained in:
nemunaire 2018-11-12 23:31:10 +01:00
commit b99a321ded
35 changed files with 11997 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
chldapasswd
config.json

51
change.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"errors"
"log"
"net/http"
)
func checkPasswdConstraint(password string) error {
if len(password) < 8 {
return errors.New("too short, please choose a password at least 8 characters long.")
}
return nil
}
func changePassword(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
displayTmpl(w, "change.html", map[string]interface{}{})
return
}
// Check the two new passwords are identical
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
displayTmplError(w, http.StatusNotAcceptable, "change.html", map[string]interface{}{"error": "New passwords are not identical. Please retry."})
} else if len(r.PostFormValue("login")) == 0 {
displayTmplError(w, http.StatusNotAcceptable, "change.html", map[string]interface{}{"error": "Please provide a valid login"})
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
displayTmplError(w, http.StatusNotAcceptable, "change.html", map[string]interface{}{"error": "The password you chose doesn't respect all constraints: " + err.Error()})
} else {
conn, err := myLDAP.Connect()
if err != nil || conn == nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "change.html", map[string]interface{}{"error": err.Error()})
} else if err := conn.ServiceBind(); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "change.html", map[string]interface{}{"error": err.Error()})
} else if dn, err := conn.SearchDN(r.PostFormValue("login")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "change.html", map[string]interface{}{"error": err.Error()})
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusUnauthorized, "change.html", map[string]interface{}{"error": err.Error()})
} else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "change.html", map[string]interface{}{"error": err.Error()})
} else {
displayMsg(w, "Password successfully changed!", http.StatusOK)
}
}
}

130
ldap.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"crypto/rand"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"github.com/amoghe/go-crypt"
"gopkg.in/ldap.v2"
)
type LDAP struct {
Host string
Port int
Starttls bool
Ssl bool
BaseDN string
ServiceDN string
ServicePassword string
}
func (l LDAP) Connect() (*LDAPConn, error) {
if l.Ssl {
if c, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port), &tls.Config{ServerName: l.Host}); err != nil {
return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
} else {
return &LDAPConn{
LDAP: l,
connection: c,
}, nil
}
} else if c, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port)); err != nil {
return nil, errors.New("unable to establish LDAP connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
} else {
if l.Starttls {
if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil {
c.Close()
return nil, errors.New("unable to StartTLS: " + err.Error())
}
}
return &LDAPConn{
LDAP: l,
connection: c,
}, nil
}
}
type LDAPConn struct {
LDAP
connection *ldap.Conn
}
func (l LDAPConn) ServiceBind() error {
return l.connection.Bind(l.ServiceDN, l.ServicePassword)
}
func (l LDAPConn) Bind(username string, password string) error {
return l.connection.Bind(username, password)
}
func (l LDAPConn) SearchDN(username string) (string, error) {
searchRequest := ldap.NewSearchRequest(
l.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username),
[]string{"dn"},
nil,
)
sr, err := l.connection.Search(searchRequest)
if err != nil {
return "", err
}
if len(sr.Entries) != 1 {
return "", errors.New("User does not exist or too many entries returned")
}
return sr.Entries[0].DN, nil
}
func (l LDAPConn) GetEntry(dn string) ([]*ldap.EntryAttribute, error) {
searchRequest := ldap.NewSearchRequest(
dn,
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
"(objectClass=*)", []string{}, nil,
)
sr, err := l.connection.Search(searchRequest)
if err != nil {
return nil, err
}
if len(sr.Entries) != 1 {
return nil, errors.New("User does not exist or too many entries returned")
}
return sr.Entries[0].Attributes, nil
}
func genSalt() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(b), nil
}
func (l LDAPConn) ChangePassword(dn string, rawpassword string) error {
salt, err := genSalt()
if err != nil {
return err
}
hashedpasswd, err := crypt.Crypt(rawpassword, "$6$" + salt + "$")
if err != nil {
return err
}
modify := ldap.NewModifyRequest(dn)
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
return l.connection.Modify(modify)
}

44
login.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
"html/template"
"log"
"net/http"
)
func tryLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
displayTmpl(w, "login.html", map[string]interface{}{})
return
}
conn, err := myLDAP.Connect()
if err != nil || conn == nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "login.html", map[string]interface{}{"error": err.Error()})
} else if err := conn.ServiceBind(); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "login.html", map[string]interface{}{"error": err.Error()})
} else if dn, err := conn.SearchDN(r.PostFormValue("login")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "login.html", map[string]interface{}{"error": err.Error()})
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusUnauthorized, "login.html", map[string]interface{}{"error": err.Error()})
} else if entries, err := conn.GetEntry(dn); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "login.html", map[string]interface{}{"error": err.Error()})
} else {
cnt := "<ul>"
for _, e := range entries {
for _, v := range e.Values {
if e.Name == "userPassword" {
cnt += "<li><strong>" + e.Name + ":</strong> <em>[...]</em></li>"
} else {
cnt += "<li><strong>" + e.Name + ":</strong> " + v + "</li>"
}
}
}
displayTmpl(w, "message.html", map[string]interface{}{"details": template.HTML(`Login ok<br><br>Here are the information we have about you:` + cnt + "</ul>")})
}
}

155
lost.go Normal file
View File

@ -0,0 +1,155 @@
package main
import (
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"io"
"net/http"
"log"
"os"
"os/exec"
"time"
"gopkg.in/gomail.v2"
)
func (l LDAPConn) genToken(dn string, previous bool) string {
hour := time.Now()
// Generate the previous token?
if previous {
hour.Add(time.Hour * -1)
}
b := make([]byte, binary.MaxVarintLen64)
binary.PutVarint(b, hour.Round(time.Hour).Unix())
// Search the email address and current password
entries, err := l.GetEntry(dn)
if err != nil {
log.Println("Unable to generate token:", err)
return "#err"
}
email := ""
curpasswd := ""
for _, e := range entries {
if e.Name == "mail" {
email += e.Values[0]
} else if e.Name == "userPassword" {
curpasswd += e.Values[0]
}
}
// Hash that
hash := sha512.New()
hash.Write(b)
hash.Write([]byte(dn))
hash.Write([]byte(email))
hash.Write([]byte(curpasswd))
return base64.StdEncoding.EncodeToString(hash.Sum(nil)[:])
}
func lostPassword(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
displayTmpl(w, "lost.html", map[string]interface{}{})
return
}
// Connect to the LDAP server
conn, err := myLDAP.Connect()
if err != nil || conn == nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()})
return
}
// Bind as service to perform the search
err = conn.ServiceBind()
if err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()})
return
}
// Search the dn of the given user
dn, err := conn.SearchDN(r.PostFormValue("login"))
if err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()})
return
}
// Generate the token
token := conn.genToken(dn, false)
// Search the email address
entries, err := conn.GetEntry(dn)
if err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()})
return
}
email := ""
cn := ""
for _, e := range entries {
if e.Name == "mail" {
email = e.Values[0]
}
if e.Name == "cn" {
cn = e.Values[0]
}
}
if email == "" {
log.Println("Unable to find a valid adress for user " + dn)
displayTmplError(w, http.StatusBadRequest, "lost.html", map[string]interface{}{"error": "We were unable to find a valid email address associated with your account. Please contact an administrator."})
return
}
// Send the email
m := gomail.NewMessage()
m.SetHeader("From", "noreply@qarnot-computing.net")
m.SetHeader("To", email)
m.SetHeader("Subject", "SSO qarnot.net: password recovery")
m.SetBody("text/plain", "Hello " + cn + "!\n\nSomeone, and we hope it's you, has requested to reset your account password.\nIn order to continue, go to: https://login.qarnot.net/reset?l=" + r.PostFormValue("login") + "&t=" + token + "\n\nBest regards,\n-- \nQarnot SSO")
// Using local sendmail: delegate to the local admin sys the responsability to transport the mail
s := gomail.SendFunc(func(from string, to []string, msg io.WriterTo) error {
cmd := exec.Command("sendmail", "-t")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
pw, err := cmd.StdinPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
var errs [3]error
_, errs[0] = m.WriteTo(pw)
errs[1] = pw.Close()
errs[2] = cmd.Wait()
for _, err = range errs {
if err != nil {
return err
}
}
return nil
})
if err := gomail.Send(s, m); err != nil {
log.Println("Unable to send email: " + err.Error())
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": "Unable to send email: " + err.Error()})
return
}
displayMsg(w, "Password recovery email sent, check your inbox.", http.StatusOK)
}

126
main.go Normal file
View File

@ -0,0 +1,126 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"strings"
"syscall"
)
var StaticDir string = "./static/"
var myLDAP = LDAP{
Host: "localhost",
Port: 389,
BaseDN: "dc=example,dc=com",
}
type ResponseWriterPrefix struct {
real http.ResponseWriter
prefix string
}
func (r ResponseWriterPrefix) Header() http.Header {
return r.real.Header()
}
func (r ResponseWriterPrefix) WriteHeader(s int) {
if v, exists := r.real.Header()["Location"]; exists {
r.real.Header().Set("Location", r.prefix + v[0])
}
r.real.WriteHeader(s)
}
func (r ResponseWriterPrefix) Write(z []byte) (int, error) {
return r.real.Write(z)
}
func StripPrefix(prefix string, h http.Handler) http.Handler {
if prefix == "" {
return h
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if prefix != "/" && r.URL.Path == "/" {
http.Redirect(w, r, prefix + "/", http.StatusFound)
} else if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
h.ServeHTTP(ResponseWriterPrefix{w, prefix}, r2)
} else {
h.ServeHTTP(w, r)
}
})
}
func main() {
var bind = flag.String("bind", "127.0.0.1:8080", "Bind port/socket")
var baseURL = flag.String("baseurl", "/", "URL prepended to each URL")
flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files")
flag.Parse()
// Sanitize options
var err error
log.Println("Checking paths...")
if StaticDir, err = filepath.Abs(StaticDir); err != nil {
log.Fatal(err)
}
if *baseURL != "/" {
tmp := path.Clean(*baseURL)
baseURL = &tmp
} else {
tmp := ""
baseURL = &tmp
}
// Load config file
if fd, err := os.Open("config.json"); err != nil {
log.Fatal(err)
} else if cnt, err := ioutil.ReadAll(fd); err != nil {
log.Fatal(err)
} else if err := json.Unmarshal(cnt, &myLDAP); err != nil {
log.Fatal(err)
}
// Prepare graceful shutdown
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
// Register handlers
http.HandleFunc(fmt.Sprintf("%s/", *baseURL), changePassword)
http.Handle(fmt.Sprintf("%s/css/", *baseURL), http.StripPrefix(*baseURL, http.FileServer(http.Dir(StaticDir))))
http.HandleFunc(fmt.Sprintf("%s/login", *baseURL), tryLogin)
http.HandleFunc(fmt.Sprintf("%s/change", *baseURL), changePassword)
http.HandleFunc(fmt.Sprintf("%s/reset", *baseURL), resetPassword)
http.HandleFunc(fmt.Sprintf("%s/lost", *baseURL), lostPassword)
srv := &http.Server{
Addr: *bind,
}
// Serve content
go func() {
log.Fatal(srv.ListenAndServe())
}()
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
// Wait shutdown signal
<-interrupt
log.Print("The service is shutting down...")
srv.Shutdown(context.Background())
log.Println("done")
}

70
reset.go Normal file
View File

@ -0,0 +1,70 @@
package main
import (
"log"
"net/http"
"strings"
)
func resetPassword(w http.ResponseWriter, r *http.Request) {
if len(r.URL.Query().Get("l")) == 0 || len(r.URL.Query().Get("t")) == 0 {
http.Redirect(w, r, "lost", http.StatusFound)
}
if r.Method != "POST" {
displayTmpl(w, "reset.html", map[string]interface{}{
"login": r.URL.Query().Get("l"),
"token": strings.Replace(r.URL.Query().Get("t"), " ", "+", -1),
})
return
}
// Check the two new passwords are identical
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
displayTmplError(w, http.StatusNotAcceptable, "reset.html", map[string]interface{}{"error": "New passwords are not identical. Please retry."})
return
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
displayTmplError(w, http.StatusNotAcceptable, "reset.html", map[string]interface{}{"error": "The password you chose doesn't respect all constraints: " + err.Error()})
return
}
// Connect to the LDAP server
conn, err := myLDAP.Connect()
if err != nil || conn == nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "reset.html", map[string]interface{}{"error": err.Error()})
return
}
// Bind as service to perform the search
err = conn.ServiceBind()
if err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "reset.html", map[string]interface{}{"error": err.Error()})
return
}
// Search the dn of the given user
dn, err := conn.SearchDN(r.PostFormValue("login"))
if err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "reset.html", map[string]interface{}{"error": err.Error()})
return
}
// Check token validity (allow current token + last one)
if conn.genToken(dn, false) != r.PostFormValue("token") && conn.genToken(dn, true) != r.PostFormValue("token") {
displayTmplError(w, http.StatusNotAcceptable, "reset.html", map[string]interface{}{"error": "Token invalid, please retry the lost password procedure. Please note that our token expires after 1 hour."})
return
}
// Replace the password by the new given
if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
log.Println(err)
displayTmplError(w, http.StatusInternalServerError, "reset.html", map[string]interface{}{"error": err.Error()})
return
}
displayMsg(w, "Password successfully changed!", http.StatusOK)
}

40
static/change.html Normal file
View File

@ -0,0 +1,40 @@
{{template "header"}}
<form method="post" action="change">
<md-card-title>
<md-card-title-text style="text-align:center">
<span class="md-headline">Change your password</span>
<span class="md-subhead">Fill the following fields!</span>
</md-card-title-text>
</md-card-title>
<md-card-content layout="column" class="layout-column">
{{if .error}}<div class="q-error" aria-hidden="true">{{.error}}</div>{{end}}
<div>
<md-input-container class="md-block">
<input name="login" required="" class="md-input" id="input_0" type="text" placeholder="Email" autofocus>
</md-input-container>
</div>
<div>
<md-input-container class="md-block">
<input name="password" required="" class="md-input" id="input_1" type="password" placeholder="Current password">
</md-input-container>
</div>
<div>
<md-input-container class="md-block">
<input name="newpassword" required="" class="md-input" id="input_2" type="password" placeholder="New password">
</md-input-container>
</div>
<div>
<md-input-container class="md-block">
<input name="new2password" required="" class="md-input" id="input_3" type="password" placeholder="Retype new password">
</md-input-container>
</div>
</md-card-content>
<md-card-actions layout="column" class="layout-column">
<button class="md-primary md-raised md-button md-ink-ripple" type="submit">Change my password</button>
<div class="q-forgot">
<a href="/lost">Forgot your password?</a>
</div>
</md-card-actions>
</form>
{{template "footer"}}

48
static/css/error.css Normal file
View File

@ -0,0 +1,48 @@
.q-error-page {
background-color: #373737;
color:#FFF;
font-family: Roboto, sans-serif;
text-align: center;
}
a {
text-decoration: none;
text-transform: uppercase;
display: inline-block;
background-color: #F44336;
color:#FFF;
border-radius: 2px;
padding:10px 20px;
margin-top:40px;
font-weight: 300;
}
.q-error-logo {
margin:40px;
}
.q-error-title {
color:#F44336;
margin:20px 0;
text-align: center;
font-weight: 400;
}
.q-error-subtitle {
margin:0;
text-align: center;
font-weight: 400;
}
.q-error-image {
max-width: 80%;
}
@media (max-width: 599px) {
.q-error-title {
font-size:20px;
}
.q-error-subtitle {
font-size:12px;
}
}

12
static/css/fonts/code_light.css Executable file
View File

@ -0,0 +1,12 @@
@font-face {
font-family: 'Code Light';
src: url('fonts/code_light/code_light-webfont.eot');
src: url('fonts/code_light/code_light-webfont.eot?#iefix') format('embedded-opentype'),
url('fonts/code_light/code_light-webfont.woff2') format('woff2'),
url('fonts/code_light/code_light-webfont.woff') format('woff'),
url('fonts/code_light/code_light-webfont.ttf') format('truetype'),
url('fonts/code_light/code_light-webfont.svg#code_lightregular') format('svg');
font-weight: normal;
font-style: normal;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

48
static/css/fonts/qarnot.css Executable file
View File

@ -0,0 +1,48 @@
@font-face {
font-family: 'qarnot';
src: url('fonts/qarnot/qarnot.eot?4d3oy1');
src: url('fonts/qarnot/qarnot.eot?4d3oy1#iefix') format('embedded-opentype'),
url('fonts/qarnot/qarnot.ttf?4d3oy1') format('truetype'),
url('fonts/qarnot/qarnot.woff?4d3oy1') format('woff'),
url('fonts/qarnot/qarnot.svg?4d3oy1#qarnot') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'qarnot' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-console:before {
content: "\e900";
}
.icon-developer:before {
content: "\e901";
}
.icon-documentation:before {
content: "\e902";
}
.icon-q:before {
content: "\e903";
}
.icon-qarnot_computing:before {
content: "\e904";
}
.icon-qarnot:before {
content: "\e905";
}
.icon-render:before {
content: "\e906";
}

Binary file not shown.

View File

@ -0,0 +1,17 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="qarnot" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="console" horiz-adv-x="908" d="M454.046-64.046c-0.010 0-0.023 0-0.035 0-14.579 0-28.239 3.936-39.974 10.803l-374.402 216.174c-23.828 14.005-39.582 39.491-39.635 68.662v432.762c0.043 29.181 15.8 54.671 39.262 68.466l375.15 216.58c11.382 6.659 25.060 10.591 39.658 10.591s28.276-3.932 40.035-10.794l374.399-216.173c23.828-14.005 39.582-39.491 39.635-68.662v-432.762c-0.053-29.178-15.807-54.665-39.262-68.466l-375.196-216.58c-11.369-6.664-25.037-10.6-39.624-10.6-0.004 0-0.007 0-0.011 0zM454.046 941.565c-0.016 0-0.036 0-0.055 0-11.176 0-21.649-3.011-30.651-8.267l-374.488-216.221c-18.297-10.748-30.392-30.32-30.417-52.719v-432.758c0.026-22.403 12.12-41.976 30.131-52.568l375.063-216.533c8.735-5.115 19.235-8.135 30.44-8.135s21.705 3.020 30.73 8.291l374.488 216.221c18.297 10.748 30.392 30.32 30.417 52.719v432.758c-0.026 22.403-12.12 41.976-30.131 52.568l-375.109 216.533c-8.717 5.1-19.194 8.111-30.374 8.111-0.015 0-0.030 0-0.045 0zM647.979 633.338c-2.148 2.349-5.227 3.817-8.648 3.817-2.275 0-4.398-0.649-6.195-1.772l-213.609-131.042c-18.121-11.201-30.014-30.958-30.014-53.494 0-34.616 28.062-62.678 62.678-62.678 20.991 0 39.572 10.319 50.947 26.161l145.9 204.167c1.419 1.873 2.273 4.242 2.273 6.81 0 3.137-1.274 5.977-3.333 8.029zM466.812 435.165c-3.848-3.588-9.028-5.79-14.723-5.79-6.223 0-11.831 2.63-15.775 6.838-3.69 3.827-5.957 9.025-5.957 14.753 0 6.247 2.696 11.864 6.988 15.752 3.87 3.596 9.050 5.793 14.743 5.793 11.959 0 21.654-9.695 21.654-21.654 0-6.266-2.662-11.911-6.916-15.865zM270.252 645.827c47.49 44.368 111.471 71.611 181.814 71.611 52.744 0 101.91-15.316 143.295-41.742l-74.812-44.524c-20.251 7.892-43.693 12.465-68.203 12.465-106.495 0-192.827-86.331-192.827-192.827s86.331-192.827 192.827-192.827c50.846 0 97.095 19.68 131.547 51.838l49.892-54.117c-46.849-41.701-108.938-67.179-176.977-67.179-147.296 0-266.704 119.407-266.704 266.704 0 74.654 30.672 142.143 80.102 190.554z" />
<glyph unicode="&#xe901;" glyph-name="developer" horiz-adv-x="908" d="M454.046-64.046c-0.010 0-0.023 0-0.035 0-14.579 0-28.239 3.936-39.974 10.803l-374.402 216.174c-23.828 14.005-39.582 39.491-39.635 68.662v432.762c0.043 29.181 15.8 54.671 39.262 68.466l375.15 216.58c11.382 6.659 25.060 10.591 39.658 10.591s28.276-3.932 40.035-10.794l374.399-216.173c23.828-14.005 39.582-39.491 39.635-68.662v-432.762c-0.053-29.178-15.807-54.665-39.262-68.466l-375.196-216.58c-11.369-6.664-25.037-10.6-39.624-10.6-0.004 0-0.007 0-0.011 0zM454.046 941.565c-0.016 0-0.036 0-0.055 0-11.176 0-21.649-3.011-30.651-8.267l-374.488-216.221c-18.297-10.748-30.392-30.32-30.417-52.719v-432.758c0.026-22.403 12.12-41.976 30.131-52.568l375.063-216.533c8.735-5.115 19.235-8.135 30.44-8.135s21.705 3.020 30.73 8.291l374.488 216.221c18.297 10.748 30.392 30.32 30.417 52.719v432.758c-0.026 22.403-12.12 41.976-30.131 52.568l-375.109 216.533c-8.717 5.1-19.194 8.111-30.374 8.111-0.015 0-0.030 0-0.045 0zM596.454 584.486v0c-5.575 5.606-9.021 13.335-9.021 21.868s3.446 16.262 9.022 21.87l28.111 27.927c0.911 1.015 1.468 2.363 1.468 3.842 0 2.788-1.98 5.113-4.611 5.646-6.653 1.935-14.254 3.045-22.114 3.045-45.128 0-81.712-36.584-81.712-81.712 0-9.986 1.791-19.554 5.070-28.399 0.071-0.078 0.219-0.823 0.219-1.602 0-1.629-0.645-3.108-1.695-4.194l-174.529-174.344c-1.054-1.070-2.518-1.733-4.138-1.733-0.802 0-1.566 0.163-2.26 0.456-8.344 3.237-18.046 5.121-28.188 5.121-22.475 0-42.789-9.251-57.342-24.153-14.912-14.842-24.13-35.361-24.13-58.034 0-8.063 1.166-15.854 3.338-23.213 0.66-1.928 2.972-3.71 5.7-3.71 1.672 0 3.188 0.669 4.293 1.755l27.882 27.882c5.606 5.575 13.335 9.021 21.868 9.021s16.262-3.446 21.87-9.022l-0.001 0.001c5.658-5.62 9.16-13.405 9.16-22.007 0-8.487-3.409-16.179-8.933-21.779l-28.063-27.648c-1.085-1.105-1.754-2.621-1.754-4.292 0-2.728 1.782-5.040 4.245-5.834 6.738-2.014 14.43-3.167 22.392-3.167 22.509 0 42.867 9.21 57.51 24.068 14.77 14.82 23.897 35.254 23.897 57.819 0 9.963-1.779 19.51-5.037 28.341-0.072 0.075-0.219 0.821-0.219 1.6 0 1.629 0.645 3.108 1.695 4.194l175.128 175.128c1.054 1.070 2.518 1.733 4.138 1.733 0.802 0 1.566-0.163 2.26-0.456 8.344-3.237 18.046-5.121 28.188-5.121 22.475 0 42.789 9.251 57.342 24.153 14.683 14.851 23.745 35.257 23.745 57.78 0 7.818-1.092 15.381-3.131 22.545-0.666 1.927-2.978 3.709-5.706 3.709-1.672 0-3.188-0.669-4.293-1.755l-28.527-27.282c-5.356-5.878-13.044-9.553-21.59-9.553-8.524 0-16.194 3.656-21.528 9.485zM389.156 456.411l53.875 53.875c1.471 1.569 2.375 3.686 2.375 6.014s-0.904 4.445-2.379 6.019l-131.85 131.895c-8.774 8.544-20.773 13.813-34.003 13.813-12.248 0-23.441-4.516-32.005-11.973-8.999-8.456-14.64-20.507-14.64-33.876 0-12.736 5.12-24.276 13.413-32.674l133.14-133.14c1.569-1.471 3.686-2.374 6.013-2.374 2.351 0 4.487 0.922 6.066 2.423zM657.98 279.53c-1.1 1.228-2.359 2.282-3.752 3.138l-23.531 12.070c-0.645 0.645-1.89 1.29-2.535 1.89l-114.203 114.157c-1.569 1.471-3.686 2.375-6.014 2.375s-4.445-0.904-6.019-2.379l-13.821-13.822c-1.471-1.569-2.375-3.686-2.375-6.014s0.904-4.445 2.379-6.019l114.106-114.106c0.645-0.645 1.29-1.89 1.89-2.535l12.029-23.458c0.898-1.466 1.952-2.725 3.164-3.811l31.078-20.937c1.563-0.972 3.459-1.548 5.49-1.548 2.781 0 5.31 1.080 7.189 2.844l13.82 13.821c1.759 1.874 2.839 4.403 2.839 7.183 0 2.031-0.576 3.928-1.574 5.535z" />
<glyph unicode="&#xe902;" glyph-name="documentation" horiz-adv-x="908" d="M454.046-64.046c-0.010 0-0.023 0-0.035 0-14.579 0-28.239 3.936-39.974 10.803l-374.402 216.174c-23.828 14.005-39.582 39.491-39.635 68.662v432.762c0.043 29.181 15.8 54.671 39.262 68.466l375.15 216.58c11.382 6.659 25.060 10.591 39.658 10.591s28.276-3.932 40.035-10.794l374.399-216.173c23.828-14.005 39.582-39.491 39.635-68.662v-432.762c-0.053-29.178-15.807-54.665-39.262-68.466l-375.196-216.58c-11.369-6.664-25.037-10.6-39.624-10.6-0.004 0-0.007 0-0.011 0zM454.046 941.565c-0.016 0-0.036 0-0.055 0-11.176 0-21.649-3.011-30.651-8.267l-374.488-216.221c-18.297-10.748-30.392-30.32-30.417-52.719v-432.758c0.026-22.403 12.12-41.976 30.131-52.568l375.063-216.533c8.735-5.115 19.235-8.135 30.44-8.135s21.705 3.020 30.73 8.291l374.488 216.221c18.297 10.748 30.392 30.32 30.417 52.719v432.758c-0.026 22.403-12.12 41.976-30.131 52.568l-375.109 216.533c-8.717 5.1-19.194 8.111-30.374 8.111-0.015 0-0.030 0-0.045 0zM188.679 544.759l259.883-133.651c3.252-1.491 7.055-2.36 11.061-2.36s7.809 0.869 11.23 2.429l259.713 133.582c6.13 3.134 6.13 8.25 0 11.383l-259.929 133.974c-3.252 1.491-7.055 2.36-11.061 2.36s-7.809-0.869-11.23-2.429l-259.667-133.766c-6.13-3.18-6.13-8.296 0-11.522zM730.566 448.3l-71.112 36.869-186.974-96.644c-3.774-1.731-8.186-2.74-12.835-2.74s-9.062 1.009-13.032 2.82l-186.869 96.241-71.112-36.869c-6.13-3.134-6.13-8.25 0-11.383l259.929-133.974c3.252-1.491 7.055-2.36 11.061-2.36s7.809 0.869 11.23 2.429l259.713 134.227c6.176 3.134 6.176 8.25 0 11.383zM730.566 340.365l-71.112 36.869-186.974-96.644c-3.774-1.731-8.186-2.74-12.835-2.74s-9.062 1.009-13.032 2.82l-186.869 96.333-71.112-36.869c-6.13-3.134-6.13-8.25 0-11.383l259.929-133.974c3.252-1.491 7.055-2.36 11.061-2.36s7.809 0.869 11.23 2.429l259.713 134.089c6.176 3.18 6.176 8.434 0 11.429z" />
<glyph unicode="&#xe903;" glyph-name="q" horiz-adv-x="908" d="M454.046-64.046c-0.010 0-0.023 0-0.035 0-14.579 0-28.239 3.936-39.974 10.803l-374.402 216.174c-23.828 14.005-39.582 39.491-39.635 68.662v432.762c0.043 29.181 15.8 54.671 39.262 68.466l375.15 216.58c11.382 6.659 25.060 10.591 39.658 10.591s28.276-3.932 40.035-10.794l374.399-216.173c23.828-14.005 39.582-39.491 39.635-68.662v-432.762c-0.053-29.178-15.807-54.665-39.262-68.466l-375.196-216.58c-11.369-6.664-25.037-10.6-39.624-10.6-0.004 0-0.007 0-0.011 0zM454.046 941.565c-0.016 0-0.036 0-0.055 0-11.176 0-21.649-3.011-30.651-8.267l-374.488-216.221c-18.297-10.748-30.392-30.32-30.417-52.719v-432.758c0.026-22.403 12.12-41.976 30.131-52.568l375.063-216.533c8.735-5.115 19.235-8.135 30.44-8.135s21.705 3.020 30.73 8.291l374.488 216.221c18.297 10.748 30.392 30.32 30.417 52.719v432.758c-0.026 22.403-12.12 41.976-30.131 52.568l-375.109 216.533c-8.717 5.1-19.194 8.111-30.374 8.111-0.015 0-0.030 0-0.045 0zM656.044 290.683c33.743 42.72 54.191 97.301 54.382 156.652-0.314 70.601-28.661 134.477-74.47 181.158-45.935 46.794-109.911 75.822-180.666 75.822s-134.731-29.028-180.668-75.824c-45.807-46.68-74.154-110.556-74.468-181.052 0-70.896 30.786-133.205 74.43-181.135 66.319-73.739 181.858-94.57 283.433-55.304l-60.42 54.797c-11.867-2.72-25.494-4.278-39.486-4.278-51.732 0-98.486 21.309-131.966 55.627-33.202 33.317-53.707 79.23-53.707 129.933s20.505 96.617 53.676 129.9c32.903 33.352 78.521 54.1 128.985 54.377 0.070 0 0.091 0 0.111 0 50.547 0 96.231-20.817 128.948-54.346 32.967-33.334 53.41-79.042 53.726-129.525 0-40.11-12.167-81.634-34.38-111.729l-85.168 77.333h-100.607l242.646-216.193 96.321-0.461z" />
<glyph unicode="&#xe904;" glyph-name="qarnot_computing" horiz-adv-x="3201" d="M1001.411 549.327h-201.030c-21.614-51.192-49.729-118.473-67.281-164.952h-87.27l224.919 567.824h59.318l225.082-567.824h-86.457zM830.771 625.384h139.274l-68.743 197.779zM1318.475 668.613h94.746c53.852 0 97.508 43.656 97.508 97.508s-43.656 97.508-97.508 97.508h-124.161v-478.928h-80.119v560.185h204.28c121.723 0 182.666-89.708 182.666-178.765 0.035-1.278 0.055-2.782 0.055-4.291 0-84.846-62.58-155.076-144.1-167.051l176.061-211.379h-104.984l-204.28 245.884v38.516zM1685.106 384.376v561.323h62.405l299.513-395.559v394.746h80.119v-560.673h-64.030l-297.238 391.171v-391.658h-81.257zM2271.944 867.205c51.447 52.436 123.052 84.937 202.249 84.937s150.801-32.501 202.205-84.892c51.539-52.076 83.354-123.672 83.354-202.699s-31.815-150.623-83.335-202.679c-51.423-52.411-123.028-84.912-202.224-84.912s-150.801 32.501-202.205 84.892c-51.539 52.076-83.354 123.672-83.354 202.699s31.815 150.623 83.335 202.679zM2618.749 809.512c-37.051 37.32-88.256 60.544-144.887 60.942-56.299-0.244-107.035-23.53-143.368-60.896-37.163-37.137-60.123-88.392-60.123-145.009s22.96-107.872 60.076-144.961c36.379-37.413 87.116-60.7 143.293-60.944 113.212 0.844 204.624 92.778 204.624 206.062 0 56.535-22.767 107.753-59.632 144.985zM2950.927 381.938v481.366h-169.664v81.257h419.611v-81.257h-169.014v-481.366h-81.257zM519.394 488.547c38.471 48.741 61.761 111.018 61.918 178.73-0.536 80.141-32.747 152.621-84.719 205.666-52.338 53.314-125.235 86.389-205.856 86.389s-153.518-33.074-205.862-86.395c-52.3-52.973-84.801-125.515-85.524-205.647 1.489-80.371 33.532-152.84 84.942-206.649 75.458-83.74 207.094-107.629 322.804-62.938l-68.093 62.405c-13.447-3.065-28.888-4.822-44.74-4.822-58.885 0-112.108 24.24-150.236 63.286-37.865 37.96-61.254 90.295-61.254 148.092s23.389 110.131 61.218 148.054c37.319 37.813 88.985 61.396 146.161 61.913 57.429-0.188 109.196-23.852 146.321-61.876 37.607-37.988 60.895-90.133 61.145-147.718 0.006-0.651 0.010-1.363 0.010-2.075 0-46.846-14.661-90.268-39.644-125.928l-97.039 88.789h-113.76l276.274-246.371h109.697zM60.618 81.775c0 75.894 0 146.262 84.832 146.262 68.093 0 69.231-56.067 69.231-81.257h-24.865c0 31.040-8.613 60.293-44.366 60.293-59.155 0-59.968-50.217-59.968-124.811s0-124.811 59.968-124.811c32.503 0 44.366 28.765 44.691 65.006h26.49c0-20.152-4.713-85.97-71.181-85.97-84.020-0.65-84.832 69.393-84.832 145.287zM496.642 227.55c84.020 0 84.832-70.043 84.832-146.262s0-146.262-84.832-146.262-84.832 70.043-84.832 146.262 0.813 146.262 84.832 146.262zM496.642-43.198c59.155 0 59.968 50.217 59.968 124.811s0 124.811-59.968 124.811-59.968-50.217-59.968-124.811 1.463-124.648 59.968-124.648zM787.867-58.637v281.474h42.416l82.882-253.522 81.257 253.522h39.653v-280.824h-23.727v255.959l-82.395-256.609h-29.74l-84.67 256.609v-256.609h-25.677zM1243.231 222.837h77.032c23.402 0 61.918-3.088 61.918-68.093 0-56.392-18.202-81.257-66.468-81.257h-47.454v-130.011h-24.865v279.361zM1268.096 93.801h46.316c26.002 0 42.741 12.514 42.741 56.392 0 47.779-20.639 51.679-43.229 51.679h-45.829v-109.047zM1597.349 45.534c0-36.566 0-88.733 57.205-88.733s57.205 52.167 57.205 88.733v177.303h24.865v-195.017c0-72.319-38.191-91.333-81.257-91.333s-81.257 19.014-81.257 91.333v195.017h24.865v-176.49zM1992.095 201.223h-68.418v21.614h162.514v-20.964h-68.418v-260.022h-24.865v260.022zM2302.009 222.837v-280.824h-24.865v280.824h24.865zM2518.315-58.637v281.474h38.191l113.76-246.534v246.534h24.865v-280.824h-37.703l-113.76 249.296v-248.484h-25.352zM3042.097 66.986h-56.717v20.964h81.257v-140.412c-23.062-7.422-49.594-11.701-77.124-11.701-0.082 0-0.163 0-0.245 0-91.32 0-84.82 91.333-84.82 146.262s-6.663 146.262 84.832 146.262c53.63 0 81.257-22.914 80.119-77.357h-24.865c0 36.891-16.251 56.392-55.255 56.392-60.618 0-59.968-54.117-59.968-124.811s0-124.811 59.968-124.811c1.083-0.028 2.358-0.044 3.637-0.044 17.496 0 34.293 2.992 49.907 8.493l-1.052 101.573z" />
<glyph unicode="&#xe905;" glyph-name="qarnot" horiz-adv-x="5609" d="M1754.696 240.409h-352.249c-37.873-89.7-87.137-207.591-117.891-289.032h-152.917l394.394 994.954h103.938l394.394-994.954h-151.778zM1455.697 373.677h244.040l-120.454 346.554zM2310.265 449.424h166.016c94.361 0 170.857 76.495 170.857 170.857s-76.495 170.857-170.857 170.857h-217.557v-839.19h-140.387v982.425h357.944c213.286 0 320.071-157.188 320.071-313.237 0.061-2.239 0.096-4.875 0.096-7.519 0-148.669-109.653-271.728-252.495-292.71l308.498-370.383h-183.956l-357.944 430.843v67.488zM2952.685-48.623v983.564h109.348l524.814-693.108v691.969h140.387v-982.425h-112.196l-520.828 685.135v-686.274h-142.38zM3980.957 797.402c90.147 91.88 215.616 148.829 354.385 148.829s264.238-56.949 354.309-148.751c90.308-91.249 146.055-216.702 146.055-355.175s-55.746-263.925-146.021-355.14c-90.105-91.837-215.573-148.786-354.342-148.786s-264.238 56.949-354.309 148.751c-90.308 91.249-146.055 216.702-146.055 355.175s55.746 263.925 146.021 355.14zM4588.636 696.311c-64.921 65.393-154.644 106.086-253.874 106.785-98.648-0.428-187.549-41.231-251.213-106.703-65.118-65.073-105.349-154.882-105.349-254.089s40.231-189.016 105.267-254.005c63.745-65.556 152.646-106.359 251.081-106.787 198.372 1.479 358.547 162.568 358.547 361.067 0 99.062-39.892 188.807-104.489 254.045zM5170.687-52.894v843.462h-297.29v142.38h735.253v-142.38h-296.151v-843.462h-142.38zM910.096 133.909c67.409 85.405 108.22 194.529 108.494 313.175-0.939 140.426-57.379 267.427-148.446 360.372-91.708 93.419-219.439 151.372-360.706 151.372s-268.999-57.954-360.716-151.383c-91.642-92.821-148.591-219.931-149.858-360.341 2.61-140.827 58.756-267.811 148.838-362.096 132.219-146.731 362.875-188.591 565.625-110.282l-119.315 109.348c-23.561-5.371-50.619-8.449-78.395-8.449-103.18 0-196.439 42.474-263.247 110.891-66.348 66.515-107.33 158.216-107.33 259.49s40.982 192.975 107.267 259.425c65.392 66.257 155.922 107.58 256.106 108.485 100.628-0.329 191.337-41.794 256.387-108.421 65.896-66.564 106.701-157.934 107.139-258.835 0.011-1.14 0.017-2.388 0.017-3.636 0-82.085-25.689-158.169-69.465-220.653l-170.035 155.578h-199.333l484.093-431.697h192.214z" />
<glyph unicode="&#xe906;" glyph-name="render" horiz-adv-x="908" d="M454.046-64.046c-0.010 0-0.023 0-0.035 0-14.579 0-28.239 3.936-39.974 10.803l-374.402 216.174c-23.828 14.005-39.582 39.491-39.635 68.662v432.762c0.043 29.181 15.8 54.671 39.262 68.466l375.15 216.58c11.382 6.659 25.060 10.591 39.658 10.591s28.276-3.932 40.035-10.794l374.399-216.173c23.828-14.005 39.582-39.491 39.635-68.662v-432.762c-0.053-29.178-15.807-54.665-39.262-68.466l-375.196-216.58c-11.369-6.664-25.037-10.6-39.624-10.6-0.004 0-0.007 0-0.011 0zM454.046 941.565c-0.016 0-0.036 0-0.055 0-11.176 0-21.649-3.011-30.651-8.267l-374.488-216.221c-18.297-10.748-30.392-30.32-30.417-52.719v-432.758c0.026-22.403 12.12-41.976 30.131-52.568l375.063-216.533c8.735-5.115 19.235-8.135 30.44-8.135s21.705 3.020 30.73 8.291l374.488 216.221c18.297 10.748 30.392 30.32 30.417 52.719v432.758c-0.026 22.403-12.12 41.976-30.131 52.568l-375.109 216.533c-8.717 5.1-19.194 8.111-30.374 8.111-0.015 0-0.030 0-0.045 0zM730.566 293.587l-100.699 59.913 2.489 182.042v4.148c-0.222 1.299-0.814 2.433-1.661 3.321-0.796 0.612-1.383 1.467-1.649 2.454-0.007 0.032-0.007 0.032-0.007 0.032-0.458 0-0.83 0.371-0.83 0.83 0 0 0 0 0 0-0.83 0.83-1.659 0.83-2.489 1.659h-0.83l-156.464 93.556v116.369c0.001 0.070 0.002 0.152 0.002 0.235 0 8.016-6.395 14.538-14.362 14.743h-0.019c-7.918-0.171-14.348-6.314-14.975-14.094l-0.004-116.561-158.723-89.039h-0.83c-0.83-0.83-1.659-0.83-2.489-1.659 0 0 0 0 0 0-0.458 0-0.83-0.371-0.83-0.83 0 0 0 0 0 0l-2.489-2.489c-0.83-0.83-0.83-2.489-1.659-3.318v-4.148l-2.35-183.102-100.884-56.594c-4.422-2.82-7.312-7.7-7.312-13.255 0-2.434 0.555-4.739 1.546-6.795 2.303-4.402 6.929-7.42 12.26-7.42 0.066 0 0.133 0 0.199 0.001 2.733 0.164 5.292 0.759 7.665 1.717l101.228 56.536 155.589-92.173h0.83c0.546-0.528 1.291-0.853 2.112-0.853 0.133 0 0.263 0.008 0.392 0.025l0.814-0.002c0.83 0 2.489-0.83 3.318-0.83v0c0.154-0.017 0.333-0.026 0.514-0.026 1.046 0 2.018 0.32 2.822 0.867l0.812-0.011c0.83 0 1.659 0.83 2.489 0.83h0.83l158.953 89.039 100.699-59.913c1.798-1.081 3.968-1.72 6.287-1.72 0.431 0 0.858 0.022 1.278 0.065 0.003-0.005 0.070-0.006 0.136-0.006 5.331 0 9.957 3.017 12.265 7.437 1.578 2.196 2.502 4.852 2.502 7.723 0 4.791-2.574 8.981-6.415 11.262zM299.471 357.647l1.659 157.294 134.804-79.868-1.659-157.294zM453.401 616.424l134.804-79.868-136.463-76.55-134.804 79.868zM465.89 433.367l136.463 76.55-1.659-157.294-136.463-76.55z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

1
static/css/qarnot-mdl-dark.min.css vendored Normal file

File diff suppressed because one or more lines are too long

4684
static/css/qarnot-mdl.css Normal file

File diff suppressed because it is too large Load Diff

1
static/css/qarnot-mdl.min.css vendored Normal file

File diff suppressed because one or more lines are too long

212
static/css/qarnot.css Normal file
View File

@ -0,0 +1,212 @@
md-tooltip {
font-size:14px;
opacity:0.8;
}
md-tooltip .md-content {
background-color: #373737;
}
.q-task-state {
border-radius: 4px;
color:#ffffff;
width:60px;
padding:4px 10px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
display:inline-block;
font-size:13px;
cursor:help;
}
.q-task-state.starting {
background-color: #00BCD4;
}
.q-task-state.running {
background-color: #F9A825;
}
.q-task-state.success {
background-color: #43A047;
}
.q-task-state.failure {
background-color: #EF5350;
}
.q-task-state.unknown {
background-color: #455A64;
}
.q-file-display {
border-radius: 50%;
color:#FFF;
background-color:#00BCD4;
padding:5px;
}
.q-file-display.q-folder {
background-color: #9399a0;
}
@-webkit-keyframes show_apps {
from {
-webkit-transform: translateY(-4%);
transform: translateY(-4%);
opacity:0;
}
to {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
@keyframes show_apps {
from {
-webkit-transform: translateY(-4%);
transform: translateY(-4%);
opacity:0;
}
to {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
.q-app-nebula {
background-color: #373737;
color:#FFF;
font-size:12px;
text-align:center;
width:360px;
padding:20px;
-webkit-animation: show_apps 0.3s cubic-bezier(.55, 0, .1, 1);
animation: show_apps 0.3s cubic-bezier(.55, 0, .1, 1);
}
.q-app-title {
font-size:20px;
margin-bottom:20px;
}
.q-app-nebula a {
color:#FFF;
text-decoration: none;
padding:10px;
display:block;
background-color: rgba(255,255,255,0.05);
border-radius:2px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.q-app-nebula a:hover {
background-color: rgba(255,255,255,0.1);
}
.q-app-nebula .q-app {
padding:6px;
}
.q-app-nebula-icon {
font-size:45px;
padding:10px;
}
.q-toast-success .md-toast-text {
font-weight: 700;
color:#66BB6A;
}
.q-toast-failure .md-toast-text {
font-weight: 700;
color:#EF5350;
}
md-toast.md-center {
left: 50%;
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
}
.q-upload-progress {
padding:20px;
position:absolute;
bottom:20px;
right:20px;
min-width:320px;
font-size:12px;
background-color:#373737;
color:#FFF;
}
q-upload-progress {
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
opacity:1;
visibility: visible;
}
q-upload-progress.hidden {
opacity:0;
visibility: hidden;
}
.q-upload-progress .q-upload-title {
font-size:18px;
margin-bottom:20px;
}
.q-upload-progress md-progress-linear {
margin-bottom:5px !important;
}
.q-upload-list {
overflow-y:auto;
max-height:400px;
padding:10px;
}
.q-payment-state {
border-radius: 4px;
color:#ffffff;
width:60px;
padding:4px 10px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
display:inline-block;
font-size:13px;
cursor:help;
}
.q-payment-state.pending {
background-color: #F9A825;
}
.q-payment-state.success {
background-color: #43A047;
}
.q-payment-state.failure {
background-color: #EF5350;
}
.q-app-promo {
text-align: center;
color:#FFFFFF;
padding-bottom:10px;
}
.q-app-promo-title {
margin-bottom:10px;
}
.q-app-promo a {
text-decoration: none;
font-size:45px;
margin:4px;
}
.q-app-promo a, .q-app-promo a:visited {
color:#FFFFFF;
}

1
static/css/qarnot.min.css vendored Normal file
View File

@ -0,0 +1 @@
md-tooltip{font-size:14px;opacity:.8}md-tooltip .md-content{background-color:#373737}.q-task-state{border-radius:4px;color:#fff;width:60px;padding:4px 10px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-align:center;display:inline-block;font-size:13px;cursor:help}.q-task-state.starting{background-color:#00bcd4}.q-task-state.running{background-color:#f9a825}.q-task-state.success{background-color:#43a047}.q-task-state.failure{background-color:#ef5350}.q-task-state.unknown{background-color:#455a64}.q-file-display{border-radius:50%;color:#fff;background-color:#00bcd4;padding:5px}.q-file-display.q-folder{background-color:#9399a0}@-webkit-keyframes show_apps{from{-webkit-transform:translateY(-4%);transform:translateY(-4%);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes show_apps{from{-webkit-transform:translateY(-4%);transform:translateY(-4%);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.q-app-nebula{background-color:#373737;color:#fff;font-size:12px;text-align:center;width:360px;padding:20px;-webkit-animation:show_apps .3s cubic-bezier(.55,0,.1,1);animation:show_apps .3s cubic-bezier(.55,0,.1,1)}.q-app-title{font-size:20px;margin-bottom:20px}.q-app-nebula a{color:#fff;text-decoration:none;padding:10px;display:block;background-color:rgba(255,255,255,.05);border-radius:2px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.q-app-nebula a:hover{background-color:rgba(255,255,255,.1)}.q-app-nebula .q-app{padding:6px}.q-app-nebula-icon{font-size:45px;padding:10px}.q-toast-success .md-toast-text{font-weight:700;color:#66bb6a}.q-toast-failure .md-toast-text{font-weight:700;color:#ef5350}md-toast.md-center{left:50%;-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}.q-upload-progress{padding:20px;position:absolute;bottom:20px;right:20px;min-width:320px;font-size:12px;background-color:#373737;color:#fff}q-upload-progress{-webkit-transition:all .3s ease;transition:all .3s ease;opacity:1;visibility:visible}q-upload-progress.hidden{opacity:0;visibility:hidden}.q-upload-progress .q-upload-title{font-size:18px;margin-bottom:20px}.q-upload-progress md-progress-linear{margin-bottom:5px!important}.q-upload-list{overflow-y:auto;max-height:400px;padding:10px}.q-payment-state{border-radius:4px;color:#fff;width:60px;padding:4px 10px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-align:center;display:inline-block;font-size:13px;cursor:help}.q-payment-state.pending{background-color:#f9a825}.q-payment-state.success{background-color:#43a047}.q-payment-state.failure{background-color:#ef5350}.q-app-promo{text-align:center;color:#fff;padding-bottom:10px}.q-app-promo-title{margin-bottom:10px}.q-app-promo a{text-decoration:none;font-size:45px;margin:4px}.q-app-promo a,.q-app-promo a:visited{color:#fff}

163
static/css/sso.css Normal file
View File

@ -0,0 +1,163 @@
body {
/*background-color: #eef1f4 !important;
background: url('../images/background.jpg') center center;
background-size: cover;*/
background-color: #373737 !important;
}
md-content {
background-color: transparent !important;
}
md-card.md-default-theme, md-card {
color: #465155;
background-color: rgb(255,255,255);
border-radius: 2px;
}
a, a:active, a:visited {
color:#00BCD4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.q-login-box {
width:90%;
max-width:430px;
}
.md-headline {
font-size:20px;
}
.q-logo {
text-align: center;
margin-bottom: 40px;
margin-top:60px;
}
.q-icon {
margin: 30px 0 0 0;
text-align: center;
}
.q-forgot {
font-size:12px;
margin-top:10px;
text-align: right;
}
.q-suite {
margin-top:30px;
text-align: center;
}
.q-suite-title {
font-size:14px;
margin-bottom:10px;
color:#fff;
}
.q-suite span {
margin:0 2px;
color:#FFF;
font-size:45px;
}
.q-error {
background-color:#F44336;
color:#FFFFFF;
border-radius:2px;
padding:10px;
text-align: center;
font-size:14px;
margin-bottom:6px;
}
.q-success {
background-color:#4CAF50;
color:#FFFFFF;
border-radius:2px;
padding:10px;
text-align: center;
font-size:14px;
margin-bottom:6px;
}
.q-back {
position:absolute;
}
.q-back .active {
display:none;
}
@keyframes fade_card {
from {
transform: translateY(+4%);
opacity:0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.animated-card {
animation: fade_card 0.6s cubic-bezier(.55, 0, .1, 1);
}
.animated-card.ng-hide-animate {
display: none !important;
}
.q-view-wrapper {
transition:height ease .3s;
overflow: hidden;
}
.q-animated-icon {
animation: fade_icon 0.6s cubic-bezier(.55, 0, .1, 1);
}
@keyframes fade_icon {
from {
transform: translateX(-100%);
opacity:0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.q-suite-register {
margin-top:20px;
}
.q-suite-register span {
margin:2px;
font-size:40px;
}
.q-services {
color:#FFF;
text-align: center;
}
.q-service-icon {
font-size:140px;
margin-bottom:20px;
}
.q-service-link {
color:#FFF !important;
}
.q-service-link:hover {
text-decoration: none;
}

6
static/css/sso.min.css vendored Executable file

File diff suppressed because one or more lines are too long

17
static/footer.html Normal file
View File

@ -0,0 +1,17 @@
{{define "footer"}}
</md-card>
<div class="q-suite ng-scope">
<div class="q-suite-title">One Account to rule them all</div>
<span class="icon-console"></span>
<span class="icon-developer"></span>
<span class="icon-documentation"></span>
<span class="icon-render"></span>
<span class="icon-q"></span>
</div></div>
</div>
</div>
</md-content>
</div>
</body>
</html>
{{end}}

31
static/header.html Normal file
View File

@ -0,0 +1,31 @@
{{define "header"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/fonts/qarnot.css">
<link rel="stylesheet" href="/css/qarnot.min.css">
<link rel="stylesheet" href="/css/sso.min.css">
<title>Qarnot password change</title>
</head>
<body>
<div layout="column" flex="" ui-view="" class="layout-column flex">
<md-content layout="column" flex="" layout-align="start center" class="_md layout-align-start-center layout-column flex">
<div class="q-login-box">
<div layout="column" flex="" class="layout-column flex">
<div class="q-logo">
<span class="icon-qarnot" style="color:#FFF;font-size:30px;"></span>
</div>
<div ui-view="" layout="column" class="layout-column"><md-card layout-padding="" class="animated-card layout-padding _md" offset="180">
<div class="q-back">
<button class="md-icon-button q-animated-icon md-button md-ink-ripple active" type="button">
<md-icon class="material-icons" role="img" aria-label="arrow_back">arrow_back</md-icon>
</button>
</div>
<div class="q-icon">
<div class="icon-q" style="font-size:120px;color:#373737"></div>
</div>
{{end}}

30
static/login.html Normal file
View File

@ -0,0 +1,30 @@
{{template "header"}}
<form method="post" action="login">
<md-card-title>
<md-card-title-text style="text-align:center">
<span class="md-headline">Sign in with your Qarnot account</span>
<span class="md-subhead">Don't have an account? <a href="/register">Create one!</a></span>
</md-card-title-text>
</md-card-title>
<md-card-content layout="column" class="layout-column">
{{if .error}}<div class="q-error" aria-hidden="true">{{.error}}</div>{{end}}
<div>
<md-input-container class="md-block">
<input name="login" required="" class="md-input" id="input_0" type="text" placeholder="Email" autofocus><div class="md-errors-spacer"></div>
</md-input-container>
</div>
<div>
<md-input-container class="md-block">
<input name="password" required="" class="md-input" id="input_1" aria-invalid="true" type="password" placeholder="Password"><div class="md-errors-spacer"></div>
</md-input-container>
</div>
</md-card-content>
<md-card-actions layout="column" class="layout-column">
<button class="md-primary md-raised md-button md-ink-ripple" type="submit">Sign In</button>
<div class="q-forgot">
<a href="/lost">Forgot your password?</a>
</div>
</md-card-actions>
</form>
{{template "footer"}}

25
static/lost.html Normal file
View File

@ -0,0 +1,25 @@
{{template "header"}}
<form method="post" action="lost">
<md-card-title>
<md-card-title-text style="text-align:center">
<span class="md-headline">Forgot your password?</span>
<span class="md-subhead">We'll send you a link by e-mail to reset it!</span>
</md-card-title-text>
</md-card-title>
<md-card-content layout="column" class="layout-column">
{{if .error}}<div class="q-error" aria-hidden="true">{{.error}}</div>{{end}}
<div>
<md-input-container class="md-block">
<input name="login" required="" class="md-input" id="input_0" type="text" placeholder="Email" autofocus><div class="md-errors-spacer"></div>
</md-input-container>
</div>
</md-card-content>
<md-card-actions layout="column" class="layout-column">
<button class="md-primary md-raised md-button md-ink-ripple" type="submit">Reset my password</button>
<div class="q-forgot">
<a href="/change">Just want to change your password?</a>
</div>
</md-card-actions>
</form>
{{template "footer"}}

7
static/message.html Normal file
View File

@ -0,0 +1,7 @@
{{template "header"}}
<md-card-content layout="column" class="layout-column">
{{if .message}}<div class="q-error success">{{.message}}</div>{{end}}
{{if .error}}<div class="q-error">{{.error}}</div>{{end}}
{{if .details}}<p>{{.details}}</p>{{end}}
</md-card-content>
{{template "footer"}}

34
static/reset.html Normal file
View File

@ -0,0 +1,34 @@
{{template "header"}}
<form method="post" action="reset">
<md-card-title>
<md-card-title-text style="text-align:center">
<span class="md-headline">Forgot your password?</span>
<span class="md-subhead">Define a new one!</span>
</md-card-title-text>
</md-card-title>
<md-card-content layout="column" class="layout-column">
{{if .error}}<div class="q-error" aria-hidden="true">{{.error}}</div>{{end}}
<div>
<md-input-container class="md-block">
<input required="" class="md-input" id="input_0" type="text" placeholder="Email" value="{{ .login }}" disabled=""><div class="md-errors-spacer"></div>
</md-input-container>
</div>
<input type="hidden" name="login" value="{{ .login }}">
<input type="hidden" name="token" value="{{ .token }}">
<div>
<md-input-container class="md-block">
<input autofocus name="newpassword" required="" class="md-input" id="input_2" type="password" placeholder="New password"><div class="md-errors-spacer"></div>
</md-input-container>
</div>
<div>
<md-input-container class="md-block">
<input name="new2password" required="" class="md-input" id="input_3" type="password" placeholder="Retype new password"><div class="md-errors-spacer"></div>
</md-input-container>
</div>
</md-card-content>
<md-card-actions layout="column" class="layout-column">
<button class="md-primary md-raised md-button md-ink-ripple" type="submit">Reset my password</button>
</md-card-actions>
</form>
{{template "footer"}}

31
tmpl.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"html/template"
"net/http"
"path"
)
func displayTmpl(w http.ResponseWriter, page string, vars map[string]interface{}) {
if t, err := template.ParseGlob(path.Join(StaticDir, "*.html")); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
t.ExecuteTemplate(w, page, vars)
}
}
func displayTmplError(w http.ResponseWriter, statusCode int, page string, vars map[string]interface{}) {
w.WriteHeader(statusCode)
displayTmpl(w, page, vars)
}
func displayMsg(w http.ResponseWriter, msg string, statusCode int) {
w.WriteHeader(statusCode)
label := "error"
if statusCode < 400 {
label = "message"
}
displayTmpl(w, "message.html", map[string]interface{}{label: msg})
}