mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2025-01-03 11:36:02 +00:00
Modernize login dialog
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
1d3f88ff47
commit
930a23f461
4 changed files with 175 additions and 81 deletions
|
@ -8,84 +8,84 @@
|
|||
<title>{{ login.Title }}</title>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
|
||||
integrity="sha256-bZLfwXAP04zRMK2BjiO8iu9pf4FbLqX6zitd+tIvLhE=" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.3.1/dist/sandstone/bootstrap.min.css"
|
||||
integrity="sha256-qgpZ1V8XkWmm9APL5rLtRW+Tyhp+0TPKJm4JMprrSOw=" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
html, body, .container, .row { height: 100%; }
|
||||
.vertical-align { display: flex; flex-direction: column; justify-content: center; }
|
||||
.modal-content { background-color: darkcyan; }
|
||||
.modal-heading h2 { color: white; }
|
||||
.nav-tabs>li>a { color: white; }
|
||||
.nav-tabs>li.active>a, .nav-tabs>li>a:hover { color: #333; }
|
||||
.tab-pane { padding-top: 10px; color: white; }
|
||||
html, body {
|
||||
background-color: #f2f2f2;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{% verbatim %}
|
||||
<div class="container h-100">
|
||||
|
||||
<div class="row vertical-align">
|
||||
<div class="col-md-offset-2 col-md-8">
|
||||
<div class="row h-100 justify-content-center align-items-center">
|
||||
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-heading">
|
||||
<h2 class="text-center">{{ login.Title }}</h2>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<div class="col-12 text-center mb-3">
|
||||
<h1>{{ login.title }}</h1>
|
||||
</div>
|
||||
<div class="card" style="width: 30rem;">
|
||||
<div class="card-header" v-if="loginMethods.length > 1">
|
||||
|
||||
<!-- Nav tabs -->
|
||||
{% if active_methods | length > 1 %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for method in active_methods sorted %}
|
||||
<li role="presentation" class="{% if method == login.DefaultMethod %}active{% endif %}">
|
||||
{% for name, desc in login.Names %}{% if method == name %}
|
||||
<a href="#{{ method }}" aria-controls="{{ method }}" role="tab" data-toggle="tab">{{ desc }}</a>
|
||||
{% endif %}{% endfor %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
{% for method, fields in active_methods sorted %}
|
||||
<div role="tabpanel" class="tab-pane {% if method == login.DefaultMethod %}active{% endif %}" id="{{ method }}">
|
||||
<form action="/login" method="post">
|
||||
{% for field in fields %}
|
||||
<div class="form-group">
|
||||
<label for="{{ method }}-{{ field.Name }}">{{ field.Label }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="{{ method }}-{{ field.Name }}"
|
||||
name="{{ method }}-{{ field.Name }}"
|
||||
{% if field.Action %}onclick="{{ field.Action }}"{% endif %}
|
||||
{% if field.Type == "button" %}
|
||||
value="{{ field.Placeholder }}"
|
||||
{% else %}
|
||||
placeholder="{{ field.Placeholder }}"
|
||||
{% endif %}
|
||||
type="{{ field.Type }}"
|
||||
/>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group text-center">
|
||||
<button type="submit" class="btn btn-success btn-lg">Login</button>
|
||||
<input type="hidden" name="go" value="{{ go }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">Login with</div>
|
||||
</div>
|
||||
<select class="form-control" v-model="active_method">
|
||||
<option
|
||||
:key="method.name"
|
||||
:value="method.name"
|
||||
v-for="method in loginMethods"
|
||||
>
|
||||
{{ method.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div> <!-- /.panel-body -->
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- ./card-header -->
|
||||
<div class="card-body">
|
||||
|
||||
</div> <!-- /.col-md-8 -->
|
||||
</div> <!-- /.row -->
|
||||
<div class="card-text">
|
||||
<form action="/login" method="post">
|
||||
|
||||
<input type="hidden" name="go" :value="go">
|
||||
<div
|
||||
class="form-group"
|
||||
:key="field.name"
|
||||
v-for="field in activeFields"
|
||||
>
|
||||
<label :for="`${active_method}-${field.name}`" v-if="field.label">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<input
|
||||
:class="fieldClasses(field)"
|
||||
:onclick="field.action"
|
||||
:id="`${active_method}-${field.name}`"
|
||||
:name="`${active_method}-${field.name}`"
|
||||
:placeholder="field.placeholder"
|
||||
:type="field.type"
|
||||
:value="field.type == 'button' ? field.placeholder : ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-center" v-if="needsLoginButton">
|
||||
<button type="submit" class="btn btn-success btn-lg">Login</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div> <!-- /.container -->
|
||||
|
||||
|
@ -93,12 +93,78 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.0/dist/jquery.min.js"
|
||||
integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"></script>
|
||||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"
|
||||
integrity="sha256-nuL8/2cJ5NDSSwnKD8VqreErSWHtnEP9E7AySL+1ev4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"
|
||||
integrity="sha256-CjSoeELFOcH0/uxWu6mC/Vlrc1AARqbm/jiiImDGV3s=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"\
|
||||
integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
|
||||
|
||||
{% endverbatim %}
|
||||
<script>
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$(e.target.hash).find('input:first').focus();
|
||||
function sortOrder(i, j) {
|
||||
switch (true) {
|
||||
case i < j:
|
||||
return -1
|
||||
case j < i:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var app = new Vue({
|
||||
computed: {
|
||||
activeFields() {
|
||||
return this.available_methods[this.active_method]
|
||||
},
|
||||
|
||||
loginMethods() {
|
||||
var res = []
|
||||
|
||||
for (name in this.available_methods) {
|
||||
res.push({
|
||||
name,
|
||||
label: this.login.names[name],
|
||||
})
|
||||
}
|
||||
|
||||
res.sort(function(i, j) { return sortOrder(i.name, j.name) })
|
||||
|
||||
return res
|
||||
},
|
||||
|
||||
needsLoginButton() {
|
||||
for (idx in this.activeFields) {
|
||||
var field = this.activeFields[idx]
|
||||
if (field.type == 'button') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
active_method: '{{ login.DefaultMethod }}',
|
||||
available_methods: {{ active_methods | to_json | safe }},
|
||||
go: '{{ go }}',
|
||||
login: {{ login | to_json | safe }},
|
||||
},
|
||||
|
||||
el: ".container",
|
||||
|
||||
methods: {
|
||||
fieldClasses(field) {
|
||||
var classes = ['form-control']
|
||||
|
||||
if (field.type == 'button') {
|
||||
classes.push('btn')
|
||||
classes.push('btn-success')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
|
10
main.go
10
main.go
|
@ -31,11 +31,11 @@ type mainConfig struct {
|
|||
Port int `yaml:"port"`
|
||||
} `yaml:"listen"`
|
||||
Login struct {
|
||||
Title string `yaml:"title"`
|
||||
DefaultMethod string `yaml:"default_method"`
|
||||
DefaultRedirect string `yaml:"default_redirect"`
|
||||
HideMFAField bool `yaml:"hide_mfa_field"`
|
||||
Names map[string]string `yaml:"names"`
|
||||
Title string `yaml:"title" json:"title"`
|
||||
DefaultMethod string `yaml:"default_method" json:"default_method"`
|
||||
DefaultRedirect string `yaml:"default_redirect" json:"default_redirect"`
|
||||
HideMFAField bool `yaml:"hide_mfa_field" json:"hide_mfa_field"`
|
||||
Names map[string]string `yaml:"names" json:"names"`
|
||||
} `yaml:"login"`
|
||||
Plugins struct {
|
||||
Directory string `yaml:"directory"`
|
||||
|
|
|
@ -48,9 +48,9 @@ type Authenticator interface {
|
|||
}
|
||||
|
||||
type LoginField struct {
|
||||
Action string
|
||||
Label string
|
||||
Name string
|
||||
Placeholder string
|
||||
Type string
|
||||
Action string `json:"action"`
|
||||
Label string `json:"label"`
|
||||
Name string `json:"name"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
|
28
pongo.go
Normal file
28
pongo.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/flosch/pongo2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pongo2.RegisterFilter("to_json", filterToJSON)
|
||||
}
|
||||
|
||||
func filterToJSON(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
err := json.NewEncoder(buf).Encode(in.Interface())
|
||||
if err != nil {
|
||||
return nil, &pongo2.Error{
|
||||
Sender: "to_json",
|
||||
OrigError: err,
|
||||
}
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(buf.String())
|
||||
return pongo2.AsValue(result), nil
|
||||
}
|
Loading…
Reference in a new issue