- local_workspace.html: supprimé Alpine.data dupliqué + ajout hamburger - workspaces.html, workspace.html, settings.html, page_editor.html: +hamburger - auth.py: +path="/" sur tous les set_cookie de session (Chrome compat) - csrf.py: +path="/" sur cookie CSRF Root cause des bugs: 1. Chrome: cookie sans path="/" → non envoyé sur certaines routes 2. Firefox: les templates écrasaient le block topbar → pas de hamburger → sidebar inaccessible sur mobile (overlay + slide-in ne fonctionnaient pas)
193 lines
8.0 KiB
HTML
193 lines
8.0 KiB
HTML
{% extends "base.html" %}
|
|
{% block page_title %}Account Settings{% endblock %}
|
|
{% block page_icon %}⚙️{% endblock %}
|
|
|
|
{% block topbar %}
|
|
<header class="topbar">
|
|
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
</button>
|
|
<div class="topbar-left">
|
|
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">
|
|
Settings
|
|
</span>
|
|
<span class="page-title-topbar">Account Settings</span>
|
|
</div>
|
|
<div class="topbar-right"></div>
|
|
</header>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<style>
|
|
.settings-layout{max-width:700px;margin:0 auto;padding:40px 24px;}
|
|
.settings-section{margin-bottom:32px;}
|
|
.settings-section h2{font-size:14px;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px;font-weight:500;}
|
|
.settings-card{background:#222;border:1px solid rgba(255,255,255,.08);border-radius:12px;overflow:hidden;}
|
|
.settings-row{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid rgba(255,255,255,.06);}
|
|
.settings-row:last-child{border-bottom:none;}
|
|
.settings-row-left{flex:1;}
|
|
.settings-row-label{font-size:14px;color:#fff;}
|
|
.settings-row-desc{font-size:12px;color:rgba(255,255,255,.45);margin-top:2px;}
|
|
.settings-row-value{font-size:13px;color:rgba(255,255,255,.5);margin-right:12px;}
|
|
.settings-input{background:#2A2A2A;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:8px 12px;color:#fff;font-size:13px;width:220px;outline:none;}
|
|
.settings-input:focus{border-color:#2383E2;}
|
|
.settings-btn{padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;}
|
|
.settings-btn-primary{background:#2383E2;color:#fff;}
|
|
.settings-btn-primary:hover{background:#2C8CEB;}
|
|
.settings-btn-danger{background:rgba(255,80,80,.15);color:#ff5050;}
|
|
.settings-btn-danger:hover{background:rgba(255,80,80,.25);}
|
|
.settings-btn-secondary{background:#333;color:#fff;}
|
|
.settings-btn-secondary:hover{background:#444;}
|
|
.inline-form{display:flex;align-items:center;gap:8px;}
|
|
.forge-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:#2A2A2A;border-radius:6px;font-size:12px;}
|
|
.connected{color:#50ff50;}
|
|
.disconnected{color:rgba(255,255,255,.3);}
|
|
</style>
|
|
|
|
<div class="settings-layout" x-data="settingsPage()">
|
|
|
|
<!-- Profile -->
|
|
<div class="settings-section">
|
|
<h2>Profile</h2>
|
|
<div class="settings-card">
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">Email</div>
|
|
<div class="settings-row-desc">Used for login and notifications</div>
|
|
</div>
|
|
<div class="settings-row-value">{{ user.email or user.login or '' }}</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">Name</div>
|
|
<div class="settings-row-desc">Display name</div>
|
|
</div>
|
|
<div class="inline-form">
|
|
<input class="settings-input" x-model="name" :value="name" placeholder="Your name">
|
|
<button class="settings-btn settings-btn-primary" @click="saveName">Save</button>
|
|
</div>
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">Password</div>
|
|
<div class="settings-row-desc">Change your password</div>
|
|
</div>
|
|
<div class="inline-form">
|
|
<input class="settings-input" type="password" x-model="newPassword" placeholder="New password">
|
|
<button class="settings-btn settings-btn-primary" @click="savePassword">Update</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Connected Forges -->
|
|
<div class="settings-section">
|
|
<h2>Connected Forges</h2>
|
|
<div class="settings-card">
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">🔗 Gitea</div>
|
|
<div class="settings-row-desc">Connect to your Gitea instance</div>
|
|
</div>
|
|
{% if gitea_connected %}
|
|
<span class="forge-badge connected">✅ Connected</span>
|
|
<button class="settings-btn settings-btn-danger" @click="disconnect('gitea')">Disconnect</button>
|
|
{% else %}
|
|
<button class="settings-btn settings-btn-primary" onclick="window.location='/auth/login?provider=gitea'">Connect</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">🐙 GitHub</div>
|
|
<div class="settings-row-desc">Connect to GitHub</div>
|
|
</div>
|
|
{% if github_connected %}
|
|
<span class="forge-badge connected">✅ Connected</span>
|
|
<button class="settings-btn settings-btn-danger" @click="disconnect('github')">Disconnect</button>
|
|
{% else %}
|
|
<button class="settings-btn settings-btn-secondary">
|
|
Coming soon
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API Tokens -->
|
|
<div class="settings-section">
|
|
<h2>API Tokens</h2>
|
|
<div class="settings-card">
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">Generate API Token</div>
|
|
<div class="settings-row-desc">For programmatic access to FlowDeck API</div>
|
|
</div>
|
|
<button class="settings-btn settings-btn-primary" @click="generateToken">Generate</button>
|
|
</div>
|
|
<template x-if="token">
|
|
<div class="settings-row" style="background:#1a2a1a">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label" style="font-family:monospace;font-size:12px;word-break:break-all" x-text="token"></div>
|
|
<div class="settings-row-desc" style="color:#50ff50">Copy this token now — it won't be shown again</div>
|
|
</div>
|
|
<button class="settings-btn settings-btn-primary" @click="copyToken">Copy</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Sessions -->
|
|
<div class="settings-section">
|
|
<h2>Active Sessions</h2>
|
|
<div class="settings-card">
|
|
<div class="settings-row">
|
|
<div class="settings-row-left">
|
|
<div class="settings-row-label">Current session</div>
|
|
<div class="settings-row-desc">You are logged in</div>
|
|
</div>
|
|
<button class="settings-btn settings-btn-danger" onclick="window.location='/auth/logout'">Log out</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
function settingsPage() {
|
|
return {
|
|
name: '{{ user.full_name or '' }}',
|
|
newPassword: '',
|
|
token: '',
|
|
async saveName() {
|
|
await fetch('/api/user/profile', {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({full_name:this.name})});
|
|
alert('Name saved');
|
|
},
|
|
async savePassword() {
|
|
if (!this.newPassword || this.newPassword.length < 6) { alert('Password must be at least 6 characters'); return; }
|
|
await fetch('/api/user/password', {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:this.newPassword})});
|
|
this.newPassword = '';
|
|
alert('Password updated');
|
|
},
|
|
async generateToken() {
|
|
const r = await fetch('/api/user/token', {method:'POST'});
|
|
const d = await r.json();
|
|
this.token = d.token;
|
|
},
|
|
copyToken() {
|
|
navigator.clipboard?.writeText(this.token);
|
|
const ta = document.createElement('textarea');
|
|
ta.value = this.token; ta.style.position='fixed'; ta.style.opacity='0';
|
|
document.body.appendChild(ta); ta.select(); document.execCommand('copy');
|
|
document.body.removeChild(ta);
|
|
alert('Token copied to clipboard');
|
|
},
|
|
async disconnect(provider) {
|
|
if (!confirm('Disconnect ' + provider + '?')) return;
|
|
await fetch('/api/user/forge/' + provider, {method:'DELETE'});
|
|
window.location.reload();
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|