feat: add first_name/last_name to users, avatar in header, neutral dashboard bg
This commit is contained in:
+3
-1
@@ -1337,6 +1337,8 @@ def create_user(payload: dict, request: Request, _: User = Depends(require_admin
|
||||
expires_at=expires_at,
|
||||
active=payload.get("active", True),
|
||||
is_admin=payload.get("is_admin", False),
|
||||
first_name=payload.get("first_name", ""),
|
||||
last_name=payload.get("last_name", ""),
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
@@ -1349,7 +1351,7 @@ def edit_user(user_id: int, payload: dict, request: Request, _: User = Depends(r
|
||||
user = db.get(User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
for key in ["username", "active", "is_admin"]:
|
||||
for key in ["username", "active", "is_admin", "first_name", "last_name"]:
|
||||
if key in payload:
|
||||
setattr(user, key, payload[key])
|
||||
if "password" in payload and payload["password"]:
|
||||
|
||||
@@ -32,6 +32,8 @@ class User(Base):
|
||||
expires_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), index=True)
|
||||
active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
first_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
last_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
|
||||
|
||||
|
||||
@@ -50,6 +52,8 @@ class Service(Base):
|
||||
icon_path: Mapped[str] = mapped_column(Text, default="")
|
||||
active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
warm_pool_size: Mapped[int] = mapped_column(Integer, default=0)
|
||||
first_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
last_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
|
||||
|
||||
|
||||
@@ -59,6 +63,8 @@ class Category(Base):
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(128), unique=True, index=True)
|
||||
slug: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
||||
first_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
last_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
|
||||
|
||||
|
||||
@@ -69,6 +75,8 @@ class ServiceCategory(Base):
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("services.id", ondelete="CASCADE"), index=True)
|
||||
category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE"), index=True)
|
||||
first_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
last_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
|
||||
|
||||
|
||||
@@ -90,6 +98,8 @@ class RdpSlot(Base):
|
||||
rdp_username: Mapped[str] = mapped_column(String(128))
|
||||
rdp_password: Mapped[str] = mapped_column(String(256), default="")
|
||||
container_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
first_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
last_name: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,22 @@ button {
|
||||
}
|
||||
.header-left { display: flex; align-items: center; }
|
||||
.header-right { display: flex; align-items: center; gap: 0.75rem; }
|
||||
|
||||
.user-avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #1e7dc8 0%, #1360a0 100%);
|
||||
color: #fff;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 8px rgba(14,80,160,0.4);
|
||||
}
|
||||
.header-username {
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
<input class="list-search" id="users_search" placeholder="Поиск пользователя..." oninput="filterList('users_search', '#users_list .user-item')" />
|
||||
<div class="list-box" id="users_list">
|
||||
{% for u in users %}
|
||||
<button class="list-item user-item" data-user-id="{{u.id}}" data-filter="{{u.username|lower}}" onclick='selectUser({{u.id}}, {{u.username|tojson}}, {{u.active|tojson}}, {{u.is_admin|tojson}}, {{u.expires_at.isoformat()|tojson}})'>
|
||||
<div>{{u.username}}</div>
|
||||
<button class="list-item user-item" data-user-id="{{u.id}}" data-filter="{{u.username|lower}}" onclick='selectUser({{u.id}}, {{u.username|tojson}}, {{u.active|tojson}}, {{u.is_admin|tojson}}, {{u.expires_at.isoformat()|tojson}}, {{u.first_name|tojson}}, {{u.last_name|tojson}})'>
|
||||
<div>{{u.username}}{% if u.first_name or u.last_name %} <small style="opacity:.6">— {{ (u.first_name + ' ' + u.last_name)|trim }}</small>{% endif %}</div>
|
||||
<small class="user-days" data-exp="{{u.expires_at.isoformat()}}"></small>
|
||||
</button>
|
||||
{% endfor %}
|
||||
@@ -66,6 +66,8 @@
|
||||
<div class="form-grid">
|
||||
<input id="u_id" type="hidden" />
|
||||
<input id="u_name" placeholder="username" />
|
||||
<input id="u_first_name" placeholder="Имя" />
|
||||
<input id="u_last_name" placeholder="Фамилия" />
|
||||
<input id="u_exp" type="date" required />
|
||||
<input id="u_pwd" placeholder="new password (optional)" type="password" />
|
||||
<select id="u_active"><option value="true">active</option><option value="false">inactive</option></select>
|
||||
@@ -91,6 +93,8 @@
|
||||
<div class="list-title">Добавить пользователя</div>
|
||||
<div class="form-grid">
|
||||
<input id="new_u_name" placeholder="username" />
|
||||
<input id="new_u_first_name" placeholder="Имя" />
|
||||
<input id="new_u_last_name" placeholder="Фамилия" />
|
||||
<input id="new_u_pwd" placeholder="password" type="password" />
|
||||
<input id="new_u_exp" type="date" required />
|
||||
<select id="new_u_active"><option value="true">active</option><option value="false">inactive</option></select>
|
||||
@@ -668,9 +672,11 @@
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function selectUser(id, username, active, isAdmin, expiresIso) {
|
||||
function selectUser(id, username, active, isAdmin, expiresIso, firstName, lastName) {
|
||||
document.getElementById('u_id').value = id;
|
||||
document.getElementById('u_name').value = username;
|
||||
document.getElementById('u_first_name').value = firstName || '';
|
||||
document.getElementById('u_last_name').value = lastName || '';
|
||||
document.getElementById('u_exp').value = dateFromIso(expiresIso);
|
||||
document.getElementById('u_pwd').value = '';
|
||||
document.getElementById('u_active').value = String(active);
|
||||
@@ -684,6 +690,8 @@
|
||||
if (!expDate) return alert('Выберите дату деактивации');
|
||||
await api('/api/admin/users', 'POST', {
|
||||
username: document.getElementById('new_u_name').value,
|
||||
first_name: document.getElementById('new_u_first_name').value,
|
||||
last_name: document.getElementById('new_u_last_name').value,
|
||||
password: document.getElementById('new_u_pwd').value,
|
||||
expires_at: expiryToApi(expDate),
|
||||
active: document.getElementById('new_u_active').value === 'true',
|
||||
@@ -699,6 +707,8 @@
|
||||
if (!expDate) return alert('Выберите дату деактивации');
|
||||
const payload = {
|
||||
username: document.getElementById('u_name').value,
|
||||
first_name: document.getElementById('u_first_name').value,
|
||||
last_name: document.getElementById('u_last_name').value,
|
||||
expires_at: expiryToApi(expDate),
|
||||
active: document.getElementById('u_active').value === 'true',
|
||||
is_admin: document.getElementById('u_admin').value === 'true',
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
<span class="header-username">{{ user.username }}</span>
|
||||
<div class="user-avatar">{{ ((user.first_name[0] if user.first_name else user.username[0]) + (user.last_name[0] if user.last_name else ''))|upper }}</div>
|
||||
<span class="header-username">{{ (user.first_name + ' ' + user.last_name)|trim or user.username }}</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
{% if user.is_admin %}
|
||||
|
||||
Reference in New Issue
Block a user