From a64d49a8c1d91f8e04c43af6bb54e0ea48c92566 Mon Sep 17 00:00:00 2001 From: Ruslan Date: Tue, 28 Apr 2026 12:20:37 +0000 Subject: [PATCH] feat: reorder card layout + svc_cred_hint field for credentials note --- app/main.py | 5 ++++- app/static/style.css | 7 +++++++ app/templates/admin.html | 26 ++++++++++++++++++++------ app/templates/dashboard.html | 8 +++++--- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/main.py b/app/main.py index 2d6af14..d98a9c5 100644 --- a/app/main.py +++ b/app/main.py @@ -199,6 +199,7 @@ class Service(Base): comment: Mapped[str] = mapped_column(Text, default="") svc_login: Mapped[str] = mapped_column(String(256), default="") svc_password: Mapped[str] = mapped_column(String(256), default="") + svc_cred_hint: Mapped[str] = mapped_column(Text, default="") 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) @@ -1259,6 +1260,7 @@ def ensure_schema_compatibility() -> None: conn.execute(text("ALTER TABLE services ADD COLUMN IF NOT EXISTS comment TEXT NOT NULL DEFAULT ''")) conn.execute(text("ALTER TABLE services ADD COLUMN IF NOT EXISTS svc_login VARCHAR(256) NOT NULL DEFAULT ''")) conn.execute(text("ALTER TABLE services ADD COLUMN IF NOT EXISTS svc_password VARCHAR(256) NOT NULL DEFAULT ''")) + conn.execute(text("ALTER TABLE services ADD COLUMN IF NOT EXISTS svc_cred_hint TEXT NOT NULL DEFAULT ''")) conn.execute(text("ALTER TABLE services ADD COLUMN IF NOT EXISTS icon_path TEXT NOT NULL DEFAULT ''")) conn.execute( text( @@ -2683,6 +2685,7 @@ def create_service(payload: dict, request: Request, _: User = Depends(require_ad comment=payload.get("comment", ""), svc_login=payload.get("svc_login", ""), svc_password=payload.get("svc_password", ""), + svc_cred_hint=payload.get("svc_cred_hint", ""), active=payload.get("active", True), warm_pool_size=max(0, int(payload.get("warm_pool_size", 0))), ) @@ -2747,7 +2750,7 @@ def edit_service(service_id: int, payload: dict, request: Request, _: User = Dep service = db.get(Service, service_id) if not service: raise HTTPException(status_code=404, detail="Service not found") - for key in ["name", "slug", "target", "active", "comment", "svc_login", "svc_password"]: + for key in ["name", "slug", "target", "active", "comment", "svc_login", "svc_password", "svc_cred_hint"]: if key in payload: setattr(service, key, payload[key]) if "type" in payload: diff --git a/app/static/style.css b/app/static/style.css index e4c5c76..a3ad02d 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -890,3 +890,10 @@ button { .svc-credentials + .tile-comment { margin-top: 0.5rem; } + +.svc-cred-hint { + margin: 0.35rem 0 0; + font-size: 0.78rem; + color: #4a7090; + line-height: 1.35; +} diff --git a/app/templates/admin.html b/app/templates/admin.html index da75bb2..5d85e33 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -123,7 +123,7 @@
{% for s in web_services %} -
@@ -725,7 +733,7 @@ location.reload(); } - function selectWebService(id, name, slug, target, comment, iconPath, active, svcLogin, svcPassword) { + function selectWebService(id, name, slug, target, comment, iconPath, active, svcLogin, svcPassword, svcCredHint) { document.getElementById('w_id').value = id; document.getElementById('w_name').value = name; document.getElementById('w_slug').value = slug; @@ -733,6 +741,7 @@ document.getElementById('w_comment').value = comment || ''; document.getElementById('w_svc_login').value = svcLogin || ''; document.getElementById('w_svc_password').value = svcPassword || ''; + document.getElementById('w_svc_cred_hint').value = svcCredHint || ''; document.getElementById('w_active').value = String(active); document.getElementById('w_icon_preview').src = iconPath || placeholderIcon; setCategoryChecks('.w_cat', serviceCategoryMap[id] || []); @@ -757,6 +766,7 @@ comment: document.getElementById('new_w_comment').value, svc_login: document.getElementById('new_w_svc_login').value, svc_password: document.getElementById('new_w_svc_password').value, + svc_cred_hint: document.getElementById('new_w_svc_cred_hint').value, category_ids: checkedCategoryIds('.new_w_cat'), active: document.getElementById('new_w_active').value === 'true', }); @@ -775,6 +785,7 @@ comment: document.getElementById('w_comment').value, svc_login: document.getElementById('w_svc_login').value, svc_password: document.getElementById('w_svc_password').value, + svc_cred_hint: document.getElementById('w_svc_cred_hint').value, category_ids: checkedCategoryIds('.w_cat'), active: document.getElementById('w_active').value === 'true', }); @@ -782,7 +793,7 @@ } function clearWebForm() { - ['w_id','w_name','w_slug','w_target','w_comment','w_svc_login','w_svc_password'].forEach(id => document.getElementById(id).value = ''); + ['w_id','w_name','w_slug','w_target','w_comment','w_svc_login','w_svc_password','w_svc_cred_hint'].forEach(id => document.getElementById(id).value = ''); document.getElementById('w_active').value = 'true'; setCategoryChecks('.w_cat', []); document.getElementById('w_icon_preview').src = placeholderIcon; @@ -865,7 +876,7 @@ location.reload(); } - function selectRdpService(id, name, slug, target, comment, iconPath, active, pool, svcLogin, svcPassword) { + function selectRdpService(id, name, slug, target, comment, iconPath, active, pool, svcLogin, svcPassword, svcCredHint) { const cfg = parseRdpTarget(target); document.getElementById('r_id').value = id; document.getElementById('r_name').value = name; @@ -878,6 +889,7 @@ document.getElementById('r_comment').value = comment || ''; document.getElementById('r_svc_login').value = svcLogin || ''; document.getElementById('r_svc_password').value = svcPassword || ''; + document.getElementById('r_svc_cred_hint').value = svcCredHint || ''; document.getElementById('r_active').value = String(active); document.getElementById('r_pool').value = pool; setCategoryChecks('.r_cat', serviceCategoryMap[id] || []); @@ -899,6 +911,7 @@ comment: document.getElementById('new_r_comment').value, svc_login: document.getElementById('new_r_svc_login').value, svc_password: document.getElementById('new_r_svc_password').value, + svc_cred_hint: document.getElementById('new_r_svc_cred_hint').value, category_ids: checkedCategoryIds('.new_r_cat'), warm_pool_size: parseInt(document.getElementById('new_r_pool').value || '0', 10), active: document.getElementById('new_r_active').value === 'true', @@ -918,6 +931,7 @@ comment: document.getElementById('r_comment').value, svc_login: document.getElementById('r_svc_login').value, svc_password: document.getElementById('r_svc_password').value, + svc_cred_hint: document.getElementById('r_svc_cred_hint').value, category_ids: checkedCategoryIds('.r_cat'), warm_pool_size: parseInt(document.getElementById('r_pool').value || '0', 10), active: document.getElementById('r_active').value === 'true', @@ -926,7 +940,7 @@ } function clearRdpForm() { - ['r_id','r_name','r_slug','r_target','r_host','r_port','r_domain','r_comment','r_pool','r_svc_login','r_svc_password'].forEach(id => document.getElementById(id).value = ''); + ['r_id','r_name','r_slug','r_target','r_host','r_port','r_domain','r_comment','r_pool','r_svc_login','r_svc_password','r_svc_cred_hint'].forEach(id => document.getElementById(id).value = ''); document.getElementById('rdp_slots_box').style.display = 'none'; document.getElementById('r_sec').value = ''; document.getElementById('r_active').value = 'true'; diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 98f7fb2..0186ddf 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -82,6 +82,7 @@
icon
+

{{ service.name }}

{% if service.svc_login or service.svc_password %}
{% if service.svc_login %} @@ -98,13 +99,14 @@
{% endif %} + {% if service.svc_cred_hint %} +

{{ service.svc_cred_hint }}

+ {% endif %} {% endif %} {% if service.comment %} - {{ service_comment_html.get(service.id, '') }} +
{{ service_comment_html.get(service.id, '') }}
{% endif %} -

{{ service.name }}

-

Открыть сервис

{% if svc_cats %}
{% for category in svc_cats %}