Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Users(db.Model, UserMixin, AuditMixin):
subject = db.Column(db.String(255))
issuer = db.Column(db.String(255))
is_deleted = db.Column(db.Boolean, default=False)
last_login = db.Column(db.DateTime)

member_of_groups: Mapped[List[Groups]] = db.relationship(
secondary=group_members, back_populates="members"
Expand Down
2 changes: 2 additions & 0 deletions apps/authentication/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def login():
# Check the password
if user and not user.is_deleted and user.password and verify_pass(password, user.password):
_check_pre_approved(user)
user.last_login = utcnow()
login_user(user)
app.logger.info(f"Successful login ipaddr={get_remote_addr()} login={identifier} auth_provider=local")
login_log = LoginLogging(ipaddr=get_remote_addr(), login=identifier, auth_provider="local", success=True)
Expand Down Expand Up @@ -123,6 +124,7 @@ def callback():
db.session.add(user)

_check_pre_approved(user)
user.last_login = utcnow()
app.logger.info(f"Successful login ipaddr={get_remote_addr()} login={subject} auth_provider={issuer} email={email}")
login_log = LoginLogging(ipaddr=get_remote_addr(), login=subject, auth_provider=issuer, success=True)
db.session.add(login_log)
Expand Down
9 changes: 3 additions & 6 deletions apps/home/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,15 +441,12 @@ def edit_lab(lab_id):
@login_required
@check_user_category(["admin", "teacher"])
def view_users():

users = Users.query.filter_by(is_deleted=False)
users_q = Users.query.filter_by(is_deleted=False)
if current_user.category in ["teacher"]:
users = users.filter(Users.category == "user")
users = users.all()

users_q = users_q.filter(Users.category == "user")
users = users_q.all()
return render_template("pages/users.html", users=users)


@blueprint.route('/labs/view', methods=["GET"])
@blueprint.route('/labs/view/<lab_id>', methods=["GET"])
@login_required
Expand Down
48 changes: 40 additions & 8 deletions apps/templates/pages/users.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ <h3 class="card-title">Current users ({{'All' if current_user.category == "admin
<th>E-mail</th>
<th>Category</th>
<th>Issuer</th>
<th>Last login</th>
<th>Created at</th>
<th>Actions</th>
</tr>
</thead>
Expand All @@ -87,6 +89,20 @@ <h3 class="card-title">Current users ({{'All' if current_user.category == "admin
<td>{{ user.email|default('', true) }}</td>
<td>{{ user.category }}</td>
<td>{{ user.issuer|default('LOCAL', true) }}</td>
<td
{% if user.last_login %}data-order="{{ user.last_login.isoformat() }}"{% else %}data-order=""{% endif %}
title="{% if user.last_login %}{{ user.last_login.isoformat() }}{% else %}—{% endif %}"
>
{% if user.last_login %}{{ user.last_login.strftime('%d/%m/%Y %H:%M') }}{% else %}&mdash;{% endif %}
</td>

<td
{% if user.created_at %}data-order="{{ user.created_at.isoformat() }}"{% else %}data-order=""{% endif %}
title="{% if user.created_at %}{{ user.created_at.isoformat() }}{% else %}—{% endif %}"
>
{% if user.created_at %}{{ user.created_at.strftime('%d/%m/%Y %H:%M') }}{% else %}&mdash;{% endif %}
</td>

<td><a href="{{url_for('home_blueprint.edit_user', user_id=user.id)}}" class="btn btn-outline-dark" role="button" aria-pressed="true"><i class="fa fa-pen"></i> Edit</a></td>
</tr>
{% endfor %}
Expand Down Expand Up @@ -186,10 +202,10 @@ <h4 class="modal-title">Confirm Approve Users</h4>
<script>
$(function () {
// === KEYS de storage
var USERS_LIST_SEARCH_KEY = 'users:list:query';
var USERS_LIST_ORDER_KEY = 'users:list:order';
var USERS_LIST_PAGE_KEY = 'users:list:page';
var USERS_LIST_LEN_KEY = 'users:list:length';
var USERS_LIST_SEARCH_KEY = 'users:list:v2:query';
var USERS_LIST_ORDER_KEY = 'users:list:v2:order';
var USERS_LIST_PAGE_KEY = 'users:list:v2:page';
var USERS_LIST_LEN_KEY = 'users:list:v2:length';

// === Helpers de storage
function getLS(key, fallback) {
Expand Down Expand Up @@ -228,7 +244,13 @@ <h4 class="modal-title">Confirm Approve Users</h4>

// === Estado salvo
var savedQuery = getLS(USERS_LIST_SEARCH_KEY, '');
var savedOrder = JSON.parse(getLS(USERS_LIST_ORDER_KEY, '[[1,"asc"]]'));
var savedOrder;
try {
savedOrder = JSON.parse(getLS(USERS_LIST_ORDER_KEY, '[[1,"asc"]]'));
} catch (e) {
savedOrder = [[1, 'asc']]; // fallback seguro (coluna Username)
setLS(USERS_LIST_ORDER_KEY, JSON.stringify(savedOrder));
}
var savedLength = parseInt(getLS(USERS_LIST_LEN_KEY, '10'), 10);
if (isNaN(savedLength) || savedLength <= 0) savedLength = 10;
var savedPage = parseInt(getLS(USERS_LIST_PAGE_KEY, '0'), 10);
Expand All @@ -247,6 +269,8 @@ <h4 class="modal-title">Confirm Approve Users</h4>
{ name: 'email' },
{ name: 'category' },
{ name: 'issuer' },
{ name: 'last_login' },
{ name: 'created_at' },
{ orderable: false, name: 'actions' },
],

Expand All @@ -255,8 +279,8 @@ <h4 class="modal-title">Confirm Approve Users</h4>
pageLength: savedLength,
displayStart: savedPage * savedLength,

stateSave: true,
stateDuration: -1
/* stateSave: true,
stateDuration: -1 */
});

(function applyNoSpellcheck(dt, savedQuery) {
Expand All @@ -283,8 +307,16 @@ <h4 class="modal-title">Confirm Approve Users</h4>
.appendTo($filter)
.on('click', function () {
table.search('').draw();
setLS(USERS_LIST_SEARCH_KEY, null);
$input.val('');

// limpar chaves v2
setLS(USERS_LIST_SEARCH_KEY, null);
setLS(USERS_LIST_PAGE_KEY, '0');
setLS(USERS_LIST_ORDER_KEY, JSON.stringify([[1, 'asc']]));
setLS(USERS_LIST_LEN_KEY, String(10));

// refletir no DataTables
table.order([[1, 'asc']]).page.len(10).page(0).draw(false);
});
}

Expand Down
26 changes: 26 additions & 0 deletions migrations/versions/2.0.7_add_last_login_to_users_2.0.8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""add last_login column on users

Revision ID: 2.0.8
Revises: 2.0.7
Create Date: 2025-12-01
"""

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "2.0.8"
down_revision = "2.0.7"
branch_labels = None
depends_on = None


def upgrade():
op.add_column(
"users",
sa.Column("last_login", sa.DateTime(), nullable=True),
)


def downgrade():
op.drop_column("users", "last_login")