UI a menu a FA
This commit is contained in:
+21
-16
@@ -253,7 +253,7 @@ def render_health_dot(status: str | None, checked_at: str | None) -> str:
|
|||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
def portal_home(user=Depends(require_user)):
|
def portal_home(user=Depends(require_user)):
|
||||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
return RedirectResponse(url="/portal/apps", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/apps", response_class=HTMLResponse)
|
@router.get("/apps", response_class=HTMLResponse)
|
||||||
@@ -373,21 +373,26 @@ def apps_page(
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</td>
|
</td>
|
||||||
<td class="actions-cell service-actions">
|
<td class="action-icons-cell">
|
||||||
<a class="btn" href="/portal/apps/{app_url_id}">Detail</a>
|
<div class="action-icons">
|
||||||
<a class="btn btn-secondary" href="/portal/deployments?app_id={app_url_id}">Nasazení</a>
|
<a class="icon-action" href="/portal/apps/{app_url_id}" title="Detail služby" aria-label="Detail služby">
|
||||||
<details class="action-menu">
|
<i class="fa-solid fa-circle-info" aria-hidden="true"></i>
|
||||||
<summary aria-label="Další akce">...</summary>
|
</a>
|
||||||
<div class="action-menu-panel">
|
<a class="icon-action" href="/portal/deployments?app_id={app_url_id}" title="Nasazení" aria-label="Nasazení">
|
||||||
<form method="post" action="/portal/apps/{app_url_id}/redeploy" onsubmit="return confirm('Spustit nové nasazení služby {app_id}?');">
|
<i class="fa-solid fa-rocket" aria-hidden="true"></i>
|
||||||
<button type="submit" class="menu-action">Nasadit znovu</button>
|
</a>
|
||||||
</form>
|
<form method="post" action="/portal/apps/{app_url_id}/redeploy" onsubmit="return confirm('Spustit nové nasazení služby {app_id}?');">
|
||||||
<form method="post" action="/portal/delete-app" onsubmit="return confirm('Smazat {app_id}? Tím se odstraní kontejner, image, workspace, záznam v katalogu a Gitea repozitář.');">
|
<button type="submit" class="icon-action" title="Nasadit znovu" aria-label="Nasadit znovu">
|
||||||
<input type="hidden" name="app_id" value="{app_id}">
|
<i class="fa-solid fa-arrows-rotate" aria-hidden="true"></i>
|
||||||
<button type="submit" class="menu-action menu-action-danger">Smazat</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
<form method="post" action="/portal/delete-app" onsubmit="return confirm('Smazat {app_id}? Tím se odstraní kontejner, image, workspace, záznam v katalogu a Gitea repozitář.');">
|
||||||
</details>
|
<input type="hidden" name="app_id" value="{app_id}">
|
||||||
|
<button type="submit" class="icon-action icon-action-danger" title="Smazat" aria-label="Smazat">
|
||||||
|
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
|
|||||||
+4
-4
@@ -33,7 +33,7 @@ GOOGLE_USERINFO_URL = "https://openidconnect.googleapis.com/v1/userinfo"
|
|||||||
@router.get("/login", response_class=HTMLResponse)
|
@router.get("/login", response_class=HTMLResponse)
|
||||||
def login_form(request: Request):
|
def login_form(request: Request):
|
||||||
if current_user(request):
|
if current_user(request):
|
||||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
return RedirectResponse(url="/portal/apps", status_code=303)
|
||||||
|
|
||||||
return _render_login()
|
return _render_login()
|
||||||
|
|
||||||
@@ -58,13 +58,13 @@ def login(request: Request, username: str = Form(...), password: str = Form(...)
|
|||||||
metadata={"username": user.get("username")},
|
metadata={"username": user.get("username")},
|
||||||
)
|
)
|
||||||
|
|
||||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
return RedirectResponse(url="/portal/apps", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/auth/gitea/login")
|
@router.get("/auth/gitea/login")
|
||||||
def gitea_login(request: Request):
|
def gitea_login(request: Request):
|
||||||
if current_user(request):
|
if current_user(request):
|
||||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
return RedirectResponse(url="/portal/apps", status_code=303)
|
||||||
return _render_login("Gitea login is disabled. Use Google sign in.")
|
return _render_login("Gitea login is disabled. Use Google sign in.")
|
||||||
|
|
||||||
gitea_url = get_gitea_public_url()
|
gitea_url = get_gitea_public_url()
|
||||||
@@ -148,7 +148,7 @@ def gitea_callback(request: Request, code: str = "", state: str = "", error: str
|
|||||||
@router.get("/auth/google/login")
|
@router.get("/auth/google/login")
|
||||||
def google_login(request: Request):
|
def google_login(request: Request):
|
||||||
if current_user(request):
|
if current_user(request):
|
||||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
return RedirectResponse(url="/portal/apps", status_code=303)
|
||||||
if get_auth_mode() == "local":
|
if get_auth_mode() == "local":
|
||||||
return _render_login("Google login is not enabled in local auth mode.")
|
return _render_login("Google login is not enabled in local auth mode.")
|
||||||
|
|
||||||
|
|||||||
@@ -816,6 +816,62 @@ input[readonly] {
|
|||||||
color: var(--danger-dark);
|
color: var(--danger-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-icons-cell {
|
||||||
|
width: 1%;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icons form {
|
||||||
|
margin: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
color: var(--secondary);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-action:hover,
|
||||||
|
.icon-action:focus-visible {
|
||||||
|
background: #eef6f9;
|
||||||
|
color: var(--secondary-dark);
|
||||||
|
border-color: var(--secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-action-danger {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-action-danger:hover,
|
||||||
|
.icon-action-danger:focus-visible {
|
||||||
|
background: var(--danger-bg);
|
||||||
|
color: var(--danger-dark);
|
||||||
|
border-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
.running-row td {
|
.running-row td {
|
||||||
background: var(--warning-bg);
|
background: var(--warning-bg);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
* Copyright 2023 Fonticons, Inc.
|
||||||
|
*/
|
||||||
|
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
|
||||||
Binary file not shown.
Binary file not shown.
+21
-18
@@ -12,33 +12,34 @@ def page(title: str, body: str, user=None) -> str:
|
|||||||
if user:
|
if user:
|
||||||
username = html.escape(user.get("username", ""))
|
username = html.escape(user.get("username", ""))
|
||||||
display_name = html.escape(user.get("display_name") or user.get("username", ""))
|
display_name = html.escape(user.get("display_name") or user.get("username", ""))
|
||||||
admin_links = ""
|
admin_menu = ""
|
||||||
if (user.get("role") or "").lower() == "admin":
|
if (user.get("role") or "").lower() == "admin":
|
||||||
admin_links = '<a href="/portal/admin/users">Users</a>'
|
admin_menu = """
|
||||||
nav = f"""
|
|
||||||
<nav>
|
|
||||||
<a class="nav-link" href="/portal/operations">Přehled</a>
|
|
||||||
<a class="nav-link" href="/portal/apps">Služby</a>
|
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
<button type="button" class="nav-menu-button">Provoz</button>
|
<button type="button" class="nav-menu-button">Admin</button>
|
||||||
<div class="nav-menu-panel">
|
<div class="nav-menu-panel">
|
||||||
<a href="/portal/jobs">Úlohy</a>
|
<a href="/portal/operations">Přehled</a>
|
||||||
|
<a href="/portal/apps">Služby</a>
|
||||||
|
<a href="/portal/jobs">Úlohy</a>
|
||||||
<a href="/portal/scheduled-scripts">Plánované skripty</a>
|
<a href="/portal/scheduled-scripts">Plánované skripty</a>
|
||||||
<a href="/portal/migration-readiness">Migration Readiness</a>
|
|
||||||
<a href="/portal/alerting/rules">Alerting</a>
|
<a href="/portal/alerting/rules">Alerting</a>
|
||||||
<a href="/portal/deployments">Nasazení</a>
|
<a href="/portal/migration-readiness">Migration Readiness</a>
|
||||||
|
<a href="/portal/deployments">Nasazení</a>
|
||||||
<a href="/portal/incidents">Incidenty</a>
|
<a href="/portal/incidents">Incidenty</a>
|
||||||
<a href="/portal/workers">Workery</a>
|
<a href="/portal/workers">Workery</a>
|
||||||
</div>
|
<a href="/portal/admin/users">Users</a>
|
||||||
</div>
|
|
||||||
<div class="nav-menu">
|
|
||||||
<button type="button" class="nav-menu-button">Správa</button>
|
|
||||||
<div class="nav-menu-panel">
|
|
||||||
{admin_links}
|
|
||||||
<a href="/portal/audit">Audit</a>
|
<a href="/portal/audit">Audit</a>
|
||||||
<a href="/portal/backups">Zálohy</a>
|
<a href="/portal/backups">Zálohy</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
"""
|
||||||
|
nav = f"""
|
||||||
|
<nav>
|
||||||
|
<a class="nav-link" href="/portal/apps">Služby</a>
|
||||||
|
<a class="nav-link" href="/portal/jobs">Úlohy</a>
|
||||||
|
<a class="nav-link" href="/portal/scheduled-scripts">Plánované skripty</a>
|
||||||
|
<a class="nav-link" href="/portal/alerting/rules">Alerting</a>
|
||||||
|
{admin_menu}
|
||||||
</nav>
|
</nav>
|
||||||
"""
|
"""
|
||||||
user_panel = f"""
|
user_panel = f"""
|
||||||
@@ -55,12 +56,14 @@ def page(title: str, body: str, user=None) -> str:
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{APP_NAME} - {html.escape(title)}</title>
|
<title>{APP_NAME} - {html.escape(title)}</title>
|
||||||
|
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/vendor/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/vendor/fontawesome/css/solid.min.css">
|
||||||
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/styles.css">
|
<link rel="stylesheet" href="{PORTAL_PREFIX}/static/styles.css">
|
||||||
<script src="{PORTAL_PREFIX}/static/portal.js"></script>
|
<script src="{PORTAL_PREFIX}/static/portal.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<a class="brand" href="/portal/operations" aria-label="{APP_NAME}">
|
<a class="brand" href="/portal/apps" aria-label="{APP_NAME}">
|
||||||
<img src="{PORTAL_PREFIX}/static/csbot-logo.svg" alt="CSBOT">
|
<img src="{PORTAL_PREFIX}/static/csbot-logo.svg" alt="CSBOT">
|
||||||
<span>{APP_NAME}</span>
|
<span>{APP_NAME}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user