feat: OAuth link mode + settings light mode CSS variables
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

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:
2026-07-13 21:24:04 -04:00
parent 6c8327c021
commit e3851b4f12
2 changed files with 31 additions and 10 deletions
+21
View File
@@ -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
View File
@@ -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">