User and Client lists show the last logins.
This commit is contained in:
parent
c2e8c0382a
commit
9aeb71d566
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
]
|
||||
|
@ -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")}
|
||||
|
@ -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(...)
|
||||
|
@ -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):
|
||||
|
@ -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>
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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) {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user