User and Client lists show the last logins.

This commit is contained in:
Amritanshu Agrawal 2020-12-05 09:03:11 +05:30
parent c2e8c0382a
commit 9aeb71d566
13 changed files with 102 additions and 61 deletions

View File

@ -1,11 +1,22 @@
from __future__ import annotations
import random
import string
import uuid
from datetime import datetime
from hashlib import md5
from typing import List, Optional
from sqlalchemy import Boolean, Column, DateTime, Integer, Unicode, UniqueConstraint
from sqlalchemy import (
Boolean,
Column,
DateTime,
Integer,
Unicode,
UniqueConstraint,
desc,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Session, relationship, synonym
from sqlalchemy.schema import ForeignKey, Table
@ -17,6 +28,26 @@ def encrypt(val):
return md5(val.encode("utf-8") + "Salt".encode("utf-8")).hexdigest()
class LoginHistory(Base):
__tablename__ = "auth_login_history"
__table_args__ = (UniqueConstraint("user_id", "client_id", "date"),)
id = Column("login_history_id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id"), nullable=False)
client_id = Column(
"client_id",
UUID(as_uuid=True),
ForeignKey("auth_clients.client_id"),
nullable=False,
)
date = Column("date", DateTime(timezone=True), nullable=False)
def __init__(self, user_id=None, client_id=None, date=None, id_=None):
self.user_id = user_id
self.client_id = client_id
self.date = datetime.utcnow() if date is None else date
self.id = id_
class Client(Base):
__tablename__ = "auth_clients"
@ -27,7 +58,7 @@ class Client(Base):
otp = Column("otp", Integer)
creation_date = Column("creation_date", DateTime(timezone=True), nullable=False)
login_history = relationship("LoginHistory", backref="client")
login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="client")
def __init__(
self,
@ -80,8 +111,8 @@ class User(Base):
_password = Column("password", Unicode(60))
locked_out = Column("disabled", Boolean)
roles = relationship("Role", secondary=user_role)
login_history = relationship("LoginHistory", backref="user")
roles: List[Role] = relationship("Role", secondary=user_role)
login_history: List[LoginHistory] = relationship("LoginHistory", order_by=desc(LoginHistory.date), backref="user")
def _get_password(self):
return self._password
@ -103,7 +134,7 @@ class User(Base):
self.id = id_
@classmethod
def auth(cls, name, password, db) -> any:
def auth(cls, name: str, password: str, db: Session) -> Optional[User]:
if password is None:
return None
user = db.query(User).filter(User.name.ilike(name)).first()
@ -115,26 +146,6 @@ class User(Base):
return user
class LoginHistory(Base):
__tablename__ = "auth_login_history"
__table_args__ = (UniqueConstraint("user_id", "client_id", "date"),)
id = Column("login_history_id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column("user_id", UUID(as_uuid=True), ForeignKey("auth_users.id"), nullable=False)
client_id = Column(
"client_id",
UUID(as_uuid=True),
ForeignKey("auth_clients.client_id"),
nullable=False,
)
date = Column("date", DateTime(timezone=True), nullable=False)
def __init__(self, user_id=None, client_id=None, date=None, id_=None):
self.user_id = user_id
self.client_id = client_id
self.date = datetime.utcnow() if date is None else date
self.id = id_
class Role(Base):
__tablename__ = "auth_roles"
@ -152,7 +163,7 @@ class Permission(Base):
id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column("name", Unicode(255), unique=True)
roles = relationship("Role", secondary=role_permission, backref="permissions")
roles: List[Role] = relationship("Role", secondary=role_permission, backref="permissions")
def __init__(self, name=None, id_=None):
self.name = name

View File

@ -1,5 +1,7 @@
import uuid
from typing import List
import brewman.schemas.client as schemas
from fastapi import APIRouter, Depends, HTTPException, Security, status
@ -74,28 +76,25 @@ def delete(
raise
@router.get("/list")
@router.get("/list", response_model=List[schemas.ClientList])
async def show_list(
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["clients"]),
):
) -> List[schemas.ClientList]:
list_ = db.query(Client).order_by(Client.name).all()
clients = []
for item in list_:
last_login = (
db.query(LoginHistory).filter(LoginHistory.client_id == item.id).order_by(desc(LoginHistory.date)).first()
)
last_login = "Never" if last_login is None else last_login.date.strftime("%d-%b-%Y %H:%M")
clients.append(
{
"id": item.id,
"code": item.code,
"name": item.name,
"enabled": item.enabled,
"otp": item.otp,
"creationDate": item.creation_date.strftime("%d-%b-%Y %H:%M"),
"lastLogin": last_login,
}
schemas.ClientList(
id=item.id,
code=item.code,
name=item.name,
enabled=item.enabled,
otp=item.otp,
creationDate=item.creation_date,
lastUser=item.login_history[0].user.name if len(item.login_history) else "None",
lastDate=item.login_history[0].date if len(item.login_history) else None,
)
)
return clients

View File

@ -164,6 +164,8 @@ async def show_list(
name=item.name,
lockedOut=item.locked_out,
roles=[p.name for p in sorted(item.roles, key=lambda p: p.name)],
lastDevice=item.login_history[0].client.name if len(item.login_history) else "None",
lastDate=item.login_history[0].date if len(item.login_history) else None,
)
for item in db.query(User).order_by(User.name).all()
]

View File

@ -3,6 +3,7 @@ import uuid
from datetime import datetime
from typing import Optional
from brewman.schemas import to_camel
from pydantic.main import BaseModel
@ -16,3 +17,13 @@ class Client(ClientIn):
id_: uuid.UUID
code: int
creation_date: datetime
class ClientList(Client):
last_user: str
last_date: Optional[datetime]
class Config:
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M")}

View File

@ -19,7 +19,7 @@ class ProductLink(BaseModel):
class ProductIn(BaseModel):
name: str = Field(..., min_length=1)
units: str
fraction: Decimal = Field(le=1, default=1)
fraction: Decimal = Field(ge=1, default=1)
fraction_units: str
product_yield: Decimal = Field(gt=0, le=1, default=1)
product_group: ProductGroupLink = Field(...)

View File

@ -1,6 +1,7 @@
import uuid
from typing import List
from datetime import datetime
from typing import List, Optional
from brewman.schemas import to_camel
from brewman.schemas.role import RoleItem
@ -31,10 +32,13 @@ class UserList(BaseModel):
id_: uuid.UUID
name: str
roles: List[str]
last_device: str
last_date: Optional[datetime]
class Config:
fields = {"id_": "id"}
anystr_strip_whitespace = True
alias_generator = to_camel
json_encoders = {datetime: lambda v: v.strftime("%d-%b-%Y %H:%M")}
class UserToken(BaseModel):

View File

@ -33,13 +33,15 @@
<!-- Created Column -->
<ng-container matColumnDef="created">
<mat-header-cell *matHeaderCellDef mat-sort-header>Created</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.created | localTime }}</mat-cell>
<mat-cell *matCellDef="let row">{{ row.creationDate | localTime }}</mat-cell>
</ng-container>
<!-- Last Login Column -->
<ng-container matColumnDef="lastLogin">
<ng-container matColumnDef="last">
<mat-header-cell *matHeaderCellDef mat-sort-header>Last Login</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.lastLogin | localTime }}</mat-cell>
<mat-cell *matCellDef="let row"
>{{ row.lastUser }} @ {{ row.lastDate ? (row.lastDate | localTime) : 'Never' }}</mat-cell
>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>

View File

@ -18,7 +18,7 @@ export class ClientListComponent implements OnInit {
list: Client[] = [];
dataSource: ClientListDataSource = new ClientListDataSource(this.list);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'lastLogin'];
displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'last'];
constructor(private route: ActivatedRoute) {}

View File

@ -3,18 +3,18 @@ export class Client {
name: string;
code: number;
enabled: boolean;
otp: number;
otp?: number;
creationDate: string;
lastLogin: string;
lastUser: string;
lastDate?: string;
public constructor(init?: Partial<Client>) {
this.id = '';
this.name = '';
this.code = 0;
this.enabled = true;
this.otp = 0;
this.creationDate = '';
this.lastLogin = '';
this.lastUser = '';
Object.assign(this, init);
}
}

View File

@ -1,9 +1,9 @@
export class UserGroup {
export class UserRole {
id: string | undefined;
name: string;
enabled: boolean;
public constructor(init?: Partial<UserGroup>) {
public constructor(init?: Partial<UserRole>) {
this.name = '';
this.enabled = true;
Object.assign(this, init);

View File

@ -1,16 +1,18 @@
import { UserGroup } from './user-group';
import { UserRole } from './user-role';
export class User {
id: string | undefined;
name: string;
password: string;
lockedOut: boolean;
roles: UserGroup[];
roles: UserRole[];
perms: string[];
isAuthenticated: boolean;
access_token?: string;
exp: number;
ver: string;
lastDevice: string;
lastDate?: string;
public constructor(init?: Partial<User>) {
this.name = '';
@ -21,6 +23,7 @@ export class User {
this.isAuthenticated = false;
this.exp = 0;
this.ver = '';
this.lastDevice = '';
Object.assign(this, init);
}
}

View File

@ -22,16 +22,25 @@
<mat-cell *matCellDef="let row">{{ row.lockedOut }}</mat-cell>
</ng-container>
<!-- Groups Column -->
<ng-container matColumnDef="groups">
<mat-header-cell *matHeaderCellDef mat-sort-header>Groups</mat-header-cell>
<!-- Roles Column -->
<ng-container matColumnDef="roles">
<mat-header-cell *matHeaderCellDef>Roles</mat-header-cell>
<mat-cell *matCellDef="let row">
<ul>
<li *ngFor="let group of row.groups">{{ group }}</li>
<li *ngFor="let role of row.roles">{{ role }}</li>
</ul>
</mat-cell>
</ng-container>
<!-- Last Login Column -->
<ng-container matColumnDef="last">
<mat-header-cell *matHeaderCellDef>Last Login</mat-header-cell>
<mat-cell *matCellDef="let row"
>{{ row.lastDevice }} @
{{ row.lastDate ? (row.lastDate | localTime) : 'Never' }}</mat-cell
>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>

View File

@ -18,7 +18,7 @@ export class UserListComponent implements OnInit {
list: User[] = [];
dataSource: UserListDataSource = new UserListDataSource(this.list);
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns = ['name', 'lockedOut', 'groups'];
displayedColumns = ['name', 'lockedOut', 'roles', 'last'];
constructor(private route: ActivatedRoute) {}