feat: OAuth link mode + settings light mode CSS variables
1. /auth/login?provider=gitea&mode=link: - Sauve 'oauth_mode=link' dans la session - Callback: link OAuth token à l'utilisateur courant (pas de nouveau compte) - Redirige vers /settings#integrations 2. Bouton Connect dans Settings → mode=link 3. Settings light mode: remplacé 12 couleurs codées en dur par CSS variables - .settings-panel, .modal-box, .settings-input - .admin-table th/td, .stat-card - .btn-sm, .btn-sm:hover
This commit is contained in:
@@ -115,6 +115,9 @@ async def login(request: Request, provider: str = Query("gitea")):
|
||||
state = secrets.token_hex(32)
|
||||
request.session["oauth_state"] = state
|
||||
request.session["oauth_provider"] = provider
|
||||
# Link mode: connect OAuth to current local account instead of creating new user
|
||||
mode = request.query_params.get("mode", "")
|
||||
request.session["oauth_mode"] = mode
|
||||
# Dynamic redirect URI based on incoming Host header
|
||||
host = request.headers.get("host", "localhost:8080")
|
||||
dynamic_redirect_uri = f"http://{host}/auth/callback"
|
||||
@@ -256,6 +259,24 @@ async def callback(
|
||||
if not oauth_user:
|
||||
return HTMLResponse("<h1>Failed to get user</h1>", status_code=400)
|
||||
|
||||
# Link mode: connect OAuth to current session user (for Settings → Integrations)
|
||||
oauth_mode = request.session.pop("oauth_mode", "")
|
||||
if oauth_mode == "link":
|
||||
from app.auth.session import get_current_user as gcu
|
||||
current = await gcu(request)
|
||||
if not current:
|
||||
return HTMLResponse("<h1>Not logged in — please log in first</h1>", status_code=400)
|
||||
from app.db import get_conn as _gc
|
||||
with _gc() as conn:
|
||||
conn.execute(
|
||||
"""INSERT OR REPLACE INTO user_oauth_tokens
|
||||
(user_id, provider, access_token, refresh_token, expires_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)""",
|
||||
(current["id"], provider_name, access_token, token_data.get("refresh_token"), token_data.get("expires_at")),
|
||||
)
|
||||
conn.commit()
|
||||
return RedirectResponse(url="/settings#integrations", status_code=302)
|
||||
|
||||
# Store user in DB
|
||||
from app.db import get_conn
|
||||
login_id = f"{provider_name}_{oauth_user['login']}"
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@
|
||||
{% block content %}
|
||||
<style>
|
||||
.settings-overlay{position:fixed;inset:0;z-index:500;display:flex;background:rgba(0,0,0,.5);backdrop-filter:blur(2px);}
|
||||
.settings-panel{display:flex;width:1050px;max-width:98vw;height:85vh;margin:auto;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.6);overflow:hidden;}
|
||||
.settings-panel{display:flex;width:1050px;max-width:98vw;height:85vh;margin:auto;background:var(--bg-primary);border:1px solid var(--border-strong);border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.6);overflow:hidden;}
|
||||
.settings-nav{width:200px;min-width:200px;background:var(--bg-sidebar);border-right:1px solid var(--border);padding:8px 0;overflow-y:auto;}
|
||||
.settings-nav-header{padding:12px 16px;font-size:13px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;}
|
||||
.settings-nav-item{display:flex;align-items:center;gap:8px;padding:6px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;transition:background 100ms;border-radius:0;}
|
||||
@@ -59,9 +59,9 @@
|
||||
|
||||
/* Modal (for tag delete/rename) */
|
||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:999;}
|
||||
.modal-box{background:#1E1E1E;border:1px solid #444;border-radius:12px;padding:24px;min-width:320px;box-shadow:0 12px 40px rgba(0,0,0,.8);}
|
||||
.modal-box{background:var(--bg-primary);border:1px solid var(--border);border-radius:12px;padding:24px;min-width:320px;box-shadow:0 12px 40px rgba(0,0,0,.8);}
|
||||
.modal-box h3{font-size:16px;font-weight:600;color:#fff;}
|
||||
.settings-input{background:#2A2A2A;border:1px solid #555;border-radius:6px;color:#ddd;font-size:13px;padding:6px 10px;outline:none;width:100%;}
|
||||
.settings-input{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;padding:6px 10px;outline:none;width:100%;}
|
||||
.settings-input:focus{border-color:var(--accent,#2383E2);box-shadow:0 0 0 2px rgba(35,131,226,.25);}
|
||||
.btn-danger{background:#E03E3E;color:#fff;border:none;padding:6px 16px;border-radius:6px;cursor:pointer;font-size:13px;}
|
||||
.btn-danger:hover{background:#C93535;}
|
||||
@@ -69,10 +69,10 @@
|
||||
/* Admin tables */
|
||||
.table-wrap{overflow-x:auto;border-radius:8px;border:1px solid #333;}
|
||||
.admin-table{width:100%;border-collapse:collapse;font-size:13px;}
|
||||
.admin-table th{background:#1A1A1A;color:var(--text-dim);font-weight:500;padding:8px 12px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;}
|
||||
.admin-table td{padding:8px 12px;border-top:1px solid #2A2A2A;color:var(--text);}
|
||||
.admin-table tr:hover td{background:#1E1E1E;}
|
||||
.stat-card{background:#1A1A1A;border:1px solid #333;border-radius:8px;padding:14px;text-align:center;}
|
||||
.admin-table th{background:var(--bg-tertiary);color:var(--text-dim);font-weight:500;padding:8px 12px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.5px;white-space:nowrap;}
|
||||
.admin-table td{padding:8px 12px;border-top:1px solid var(--border);color:var(--text);}
|
||||
.admin-table tr:hover td{background:var(--bg-secondary);}
|
||||
.stat-card{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;padding:14px;text-align:center;}
|
||||
.stat-value{font-size:22px;font-weight:700;color:var(--accent,#2383E2);}
|
||||
.stat-label{font-size:11px;color:var(--text-dim);margin-top:2px;text-transform:uppercase;letter-spacing:.5px;}
|
||||
.role-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:500;}
|
||||
@@ -81,8 +81,8 @@
|
||||
.status-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:5px;vertical-align:middle;}
|
||||
.status-dot.active{background:#0F7B6C;}
|
||||
.status-dot.inactive{background:#E03E3E;}
|
||||
.btn-sm{background:transparent;border:1px solid #444;color:var(--text-dim);padding:2px 6px;border-radius:4px;cursor:pointer;font-size:12px;}
|
||||
.btn-sm:hover{background:#333;color:var(--text);}
|
||||
.btn-sm{background:transparent;border:1px solid var(--border);color:var(--text-dim);padding:2px 6px;border-radius:4px;cursor:pointer;font-size:12px;}
|
||||
.btn-sm:hover{background:var(--bg-tertiary);color:var(--text);}
|
||||
.btn-sm-danger{color:#E03E3E;border-color:#542;}
|
||||
|
||||
/* Close button */
|
||||
@@ -343,7 +343,7 @@
|
||||
<div class="setting-control">
|
||||
<span x-show="giteaLinked" style="font-size:12px;color:#0F7B6C;">✅ Active</span>
|
||||
<button x-show="giteaLinked" class="btn-sm" style="color:var(--danger);" @click="disconnectGitea()">Disconnect</button>
|
||||
<a x-show="!giteaLinked" class="btn-sm" href="/auth/login?provider=gitea" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
||||
<a x-show="!giteaLinked" class="btn-sm" href="/auth/login?provider=gitea&mode=link" style="text-decoration:none;color:var(--accent);">🔗 Connect</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
|
||||
Reference in New Issue
Block a user