Add page to see login attempts for a company
This commit is contained in:
parent
f7fdc594d5
commit
a11ca5b470
|
@ -0,0 +1,28 @@
|
|||
-- Deploy camper:company_login_attempt to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_camper
|
||||
-- requires: login_attempt
|
||||
-- requires: user
|
||||
-- requires: company_user
|
||||
-- requires: current_company_id
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to camper, public;
|
||||
|
||||
create or replace view company_login_attempt with (security_barrier) as
|
||||
select attempt_id
|
||||
, user_name
|
||||
, ip_address
|
||||
, success
|
||||
, attempted_at
|
||||
from auth.login_attempt
|
||||
join auth."user" on "user".email = user_name
|
||||
join company_user using (user_id)
|
||||
where company_id = current_company_id()
|
||||
;
|
||||
;
|
||||
|
||||
grant select on table company_login_attempt to admin;
|
||||
|
||||
commit;
|
|
@ -35,6 +35,13 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
|
|||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
||||
}
|
||||
case "login-attempts":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
serveLoginAttemptIndex(w, r, user, company, conn)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
||||
}
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
)
|
||||
|
||||
func serveLoginAttemptIndex(w http.ResponseWriter, r *http.Request, loginAttempt *auth.User, company *auth.Company, conn *database.Conn) {
|
||||
loginAttempts, err := collectLoginAttemptEntries(r.Context(), conn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loginAttempts.MustRender(w, r, loginAttempt, company)
|
||||
}
|
||||
|
||||
func collectLoginAttemptEntries(ctx context.Context, conn *database.Conn) (loginAttemptIndex, error) {
|
||||
rows, err := conn.Query(ctx, `
|
||||
select user_name
|
||||
, host(ip_address)
|
||||
, attempted_at
|
||||
, success
|
||||
from company_login_attempt
|
||||
order by attempted_at desc
|
||||
limit 500
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries loginAttemptIndex
|
||||
for rows.Next() {
|
||||
entry := &loginAttemptEntry{}
|
||||
if err = rows.Scan(&entry.UserName, &entry.IPAddress, &entry.Date, &entry.Success); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
type loginAttemptEntry struct {
|
||||
UserName string
|
||||
IPAddress string
|
||||
Date time.Time
|
||||
Success bool
|
||||
}
|
||||
|
||||
type loginAttemptIndex []*loginAttemptEntry
|
||||
|
||||
func (page *loginAttemptIndex) MustRender(w http.ResponseWriter, r *http.Request, loginAttempt *auth.User, company *auth.Company) {
|
||||
template.MustRenderAdmin(w, r, loginAttempt, company, "user/login-attempts.gohtml", page)
|
||||
}
|
42
po/ca.po
42
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-01-17 19:40+0100\n"
|
||||
"POT-Creation-Date: 2024-01-17 20:25+0100\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -512,7 +512,6 @@ msgid "Name"
|
|||
msgstr "Nom"
|
||||
|
||||
#: web/templates/admin/legal/index.gohtml:29
|
||||
#: web/templates/admin/user/index.gohtml:42
|
||||
msgid "No legal texts added yet."
|
||||
msgstr "No s’ha afegit cap text legal encara."
|
||||
|
||||
|
@ -763,12 +762,14 @@ msgstr "Tipus"
|
|||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
|
@ -1062,8 +1063,35 @@ msgctxt "input"
|
|||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:6
|
||||
#: web/templates/admin/user/login-attempts.gohtml:11
|
||||
msgctxt "title"
|
||||
msgid "Login Attempts"
|
||||
msgstr "Intents d’entrada"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:15
|
||||
msgctxt "header"
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:16
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "Correu-e"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:17
|
||||
msgctxt "header"
|
||||
msgid "IP Address"
|
||||
msgstr "Adreça IP"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Success"
|
||||
msgstr "Èxit"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:6
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
#: web/templates/admin/user/index.gohtml:13
|
||||
#: web/templates/admin/layout.gohtml:70
|
||||
msgctxt "title"
|
||||
msgid "Users"
|
||||
|
@ -1074,10 +1102,10 @@ msgctxt "action"
|
|||
msgid "Add User"
|
||||
msgstr "Afegeix usuari"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "Correu-e"
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
msgctxt "action"
|
||||
msgid "Logs"
|
||||
msgstr "Registres"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:19
|
||||
msgctxt "header"
|
||||
|
|
44
po/es.po
44
po/es.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-01-17 19:40+0100\n"
|
||||
"POT-Creation-Date: 2024-01-17 20:25+0100\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -512,7 +512,6 @@ msgid "Name"
|
|||
msgstr "Nombre"
|
||||
|
||||
#: web/templates/admin/legal/index.gohtml:29
|
||||
#: web/templates/admin/user/index.gohtml:42
|
||||
msgid "No legal texts added yet."
|
||||
msgstr "No se ha añadido ningún texto legal todavía."
|
||||
|
||||
|
@ -763,12 +762,14 @@ msgstr "Tipo"
|
|||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
|
@ -1062,22 +1063,49 @@ msgctxt "input"
|
|||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:6
|
||||
#: web/templates/admin/user/login-attempts.gohtml:11
|
||||
msgctxt "title"
|
||||
msgid "Login Attempts"
|
||||
msgstr "Intentos de entrada"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:15
|
||||
msgctxt "header"
|
||||
msgid "Date"
|
||||
msgstr "Fecha"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:16
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "Correo-e"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:17
|
||||
msgctxt "header"
|
||||
msgid "IP Address"
|
||||
msgstr "Dirección IP"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Success"
|
||||
msgstr "Éxito"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:6
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
#: web/templates/admin/user/index.gohtml:13
|
||||
#: web/templates/admin/layout.gohtml:70
|
||||
msgctxt "title"
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
msgstr "Usuarios"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:11
|
||||
msgctxt "action"
|
||||
msgid "Add User"
|
||||
msgstr "Añadir usuario"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "Correo-e"
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
msgctxt "action"
|
||||
msgid "Logs"
|
||||
msgstr "Registros"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:19
|
||||
msgctxt "header"
|
||||
|
|
42
po/fr.po
42
po/fr.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2024-01-17 19:40+0100\n"
|
||||
"POT-Creation-Date: 2024-01-17 20:25+0100\n"
|
||||
"PO-Revision-Date: 2023-12-20 10:13+0100\n"
|
||||
"Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n"
|
||||
"Language-Team: French <traduc@traduc.org>\n"
|
||||
|
@ -513,7 +513,6 @@ msgid "Name"
|
|||
msgstr "Nom"
|
||||
|
||||
#: web/templates/admin/legal/index.gohtml:29
|
||||
#: web/templates/admin/user/index.gohtml:42
|
||||
msgid "No legal texts added yet."
|
||||
msgstr "Aucune texte juridique n’a encore été ajoutée."
|
||||
|
||||
|
@ -764,12 +763,14 @@ msgstr "Type"
|
|||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:49
|
||||
#: web/templates/admin/season/index.gohtml:41
|
||||
#: web/templates/admin/user/login-attempts.gohtml:27
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
|
@ -1063,8 +1064,35 @@ msgctxt "input"
|
|||
msgid "Language"
|
||||
msgstr "Langue"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:6
|
||||
#: web/templates/admin/user/login-attempts.gohtml:11
|
||||
msgctxt "title"
|
||||
msgid "Login Attempts"
|
||||
msgstr "Tentatives de connexion"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:15
|
||||
msgctxt "header"
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:16
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:17
|
||||
msgctxt "header"
|
||||
msgid "IP Address"
|
||||
msgstr "Adresse IP"
|
||||
|
||||
#: web/templates/admin/user/login-attempts.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Success"
|
||||
msgstr "Succès"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:6
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
#: web/templates/admin/user/index.gohtml:13
|
||||
#: web/templates/admin/layout.gohtml:70
|
||||
msgctxt "title"
|
||||
msgid "Users"
|
||||
|
@ -1075,10 +1103,10 @@ msgctxt "action"
|
|||
msgid "Add User"
|
||||
msgstr "Ajouter un utilisateur"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:18
|
||||
msgctxt "header"
|
||||
msgid "Email"
|
||||
msgstr "E-mail"
|
||||
#: web/templates/admin/user/index.gohtml:12
|
||||
msgctxt "action"
|
||||
msgid "Logs"
|
||||
msgstr "Journaux"
|
||||
|
||||
#: web/templates/admin/user/index.gohtml:19
|
||||
msgctxt "header"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert camper:company_login_attempt from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop view if exists camper.company_login_attempt;
|
||||
|
||||
commit;
|
|
@ -155,3 +155,4 @@ translate_cover_carousel_slide [roles schema_camper cover_carousel_i18n] 2024-01
|
|||
remove_cover_carousel_slide [roles schema_camper cover_carousel cover_carousel_i18n] 2024-01-16T18:27:48Z jordi fita mas <jordi@tandem.blog> # Add function to remove sliders from the cover carousel
|
||||
order_cover_carousel [schema_camper roles cover_carousel] 2024-01-16T18:40:12Z jordi fita mas <jordi@tandem.blog> # Add function to order cover carousel
|
||||
company_user_profile [roles schema_camper user company_user current_company_id] 2024-01-17T17:37:19Z jordi fita mas <jordi@tandem.blog> # Add view to list users for admins
|
||||
company_login_attempt [roles schema_camper login_attempt user company_user current_company_id] 2024-01-17T19:02:26Z jordi fita mas <jordi@tandem.blog> # Add view to see login attempts for current company
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
-- Test company_login_attempt
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(17);
|
||||
|
||||
set search_path to camper, auth, public;
|
||||
|
||||
select has_view('company_login_attempt');
|
||||
select table_privs_are('company_login_attempt', 'guest', array []::text[]);
|
||||
select table_privs_are('company_login_attempt', 'employee', array []::text[]);
|
||||
select table_privs_are('company_login_attempt', 'admin', array ['SELECT']);
|
||||
select table_privs_are('company_login_attempt', 'authenticator', array []::text[]);
|
||||
|
||||
select has_column('company_login_attempt', 'attempt_id');
|
||||
select col_type_is('company_login_attempt', 'attempt_id', 'bigint');
|
||||
|
||||
select has_column('company_login_attempt', 'user_name');
|
||||
select col_type_is('company_login_attempt', 'user_name', 'text');
|
||||
|
||||
select has_column('company_login_attempt', 'ip_address');
|
||||
select col_type_is('company_login_attempt', 'ip_address', 'inet');
|
||||
|
||||
select has_column('company_login_attempt', 'success');
|
||||
select col_type_is('company_login_attempt', 'success', 'boolean');
|
||||
|
||||
select has_column('company_login_attempt', 'attempted_at');
|
||||
select col_type_is('company_login_attempt', 'attempted_at', 'timestamp with time zone');
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate company_host cascade;
|
||||
truncate company_user cascade;
|
||||
truncate company cascade;
|
||||
truncate auth."user" cascade;
|
||||
truncate auth.login_attempt cascade;
|
||||
reset client_min_messages;
|
||||
|
||||
insert into auth."user" (user_id, email, name, password, cookie, cookie_expires_at, lang_tag)
|
||||
values (1, 'demo@tandem.blog', 'Demo', 'test', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month', 'ca')
|
||||
, (5, 'admin@tandem.blog', 'Admin', 'test', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month', 'es')
|
||||
, (7, 'another@tandem.blog', 'Another Employee', 'test', default, default, default)
|
||||
;
|
||||
|
||||
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
|
||||
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 60, 'ES', 'EUR', 'ca')
|
||||
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', '', 60, 'FR', 'USD', 'es')
|
||||
;
|
||||
|
||||
insert into company_user (company_id, user_id, role)
|
||||
values (2, 1, 'admin')
|
||||
, (4, 5, 'admin')
|
||||
, (4, 7, 'employee')
|
||||
;
|
||||
|
||||
insert into company_host (company_id, host)
|
||||
values (2, 'co2')
|
||||
, (4, 'co4')
|
||||
;
|
||||
|
||||
prepare login_attempt as
|
||||
select user_name, ip_address, success, attempted_at
|
||||
from company_login_attempt
|
||||
;
|
||||
|
||||
select login('demo@tandem.blog', 'test', '::1'::inet);
|
||||
select login('demo@tandem.blog', '123', '127.0.0.1'::inet);
|
||||
select login('admin@tandem.blog', '123', '192.168.1.1'::inet);
|
||||
select login('admin@tandem.blog', 'test', '::1'::inet);
|
||||
select login('admin@tandem.blog', 'test', '192.168.2.1'::inet);
|
||||
select login('another@tandem.blog', 'test', '192.168.3.1'::inet);
|
||||
select login('unknown@tandem.blog', 'test', '192.168.4.1'::inet);
|
||||
|
||||
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
|
||||
|
||||
select bag_eq(
|
||||
'login_attempt',
|
||||
$$ values ('demo@tandem.blog', '::1'::inet, true, current_timestamp)
|
||||
, ('demo@tandem.blog', '127.0.0.1'::inet, false, current_timestamp)
|
||||
$$,
|
||||
'Should only see login attempts from the first company'
|
||||
);
|
||||
|
||||
reset role;
|
||||
|
||||
select set_cookie('12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524/admin@tandem.blog', 'co4');
|
||||
|
||||
select bag_eq(
|
||||
'login_attempt',
|
||||
$$ values ('admin@tandem.blog', '192.168.1.1'::inet, false, current_timestamp)
|
||||
, ('admin@tandem.blog', '::1'::inet, true, current_timestamp)
|
||||
, ('admin@tandem.blog', '192.168.2.1'::inet, true, current_timestamp)
|
||||
, ('another@tandem.blog', '192.168.3.1'::inet, true, current_timestamp)
|
||||
$$,
|
||||
'Should only see login_attempts from the second company'
|
||||
);
|
||||
|
||||
reset role;
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,14 @@
|
|||
-- Verify camper:company_login_attempt on pg
|
||||
|
||||
begin;
|
||||
|
||||
select attempt_id
|
||||
, user_name
|
||||
, ip_address
|
||||
, success
|
||||
, attempted_at
|
||||
from camper.company_login_attempt
|
||||
where false
|
||||
;
|
||||
|
||||
rollback;
|
|
@ -9,8 +9,8 @@
|
|||
{{ define "content" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/user.userIndex*/ -}}
|
||||
<a href="/admin/users/new">{{( pgettext "Add User" "action" )}}</a>
|
||||
<a href="/admin/users/login-attempts">{{( pgettext "Logs" "action" )}}</a>
|
||||
<h2>{{( pgettext "Users" "title" )}}</h2>
|
||||
{{ if .Users -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -38,7 +38,4 @@
|
|||
{{- end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ else -}}
|
||||
<p>{{( gettext "No legal texts added yet." )}}</p>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
{{ define "title" -}}
|
||||
{{( pgettext "Login Attempts" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/user.loginAttemptIndex*/ -}}
|
||||
<h2>{{( pgettext "Login Attempts" "title" )}}</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{( pgettext "Date" "header" )}}</th>
|
||||
<th scope="col">{{( pgettext "Email" "header" )}}</th>
|
||||
<th scope="col">{{( pgettext "IP Address" "header" )}}</th>
|
||||
<th scope="col">{{( pgettext "Success" "header" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range . -}}
|
||||
<tr>
|
||||
<td>{{ .Date }}</td>
|
||||
<td>{{ .UserName }}</td>
|
||||
<td>{{ .IPAddress }}</td>
|
||||
<td>{{ if .Success }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{- end }}
|
Loading…
Reference in New Issue