Files
Tony 5f72264b06 feat: add organization management features and password reset functionality
- Introduced a new section for managing organizations, including creating organizations and adding users to them.
- Added a password reset modal and functionality to request a password reset link.
- Updated the settings page to include personal settings for changing passwords and admin settings for configuring registration options.
- Enhanced the app detail view with tabs for releases, insights, collaborators, tracks, and settings.
- Improved user management with admin capabilities to verify user emails and change user roles.
- Updated navigation and UI elements to accommodate new features and improve user experience.
2026-06-12 20:03:58 +08:00

407 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shorebird Self-Hosted</title>
<link rel="stylesheet" href="/web/css/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%230891b2'/><text x='50%25' y='55%25' text-anchor='middle' dy='.1em' font-size='20' fill='white'>🐦</text></svg>">
</head>
<body>
<!-- ============================================================
LOGIN PAGE
============================================================ -->
<div id="loginPage" class="login-container">
<div class="login-card">
<div class="logo-icon">🐦</div>
<h1>Shorebird</h1>
<p class="subtitle">Self-Hosted Dashboard</p>
<div id="loginError" class="login-error"></div>
<form id="loginForm">
<div class="form-group">
<label for="loginEmail">Email</label>
<input type="email" id="loginEmail" placeholder="admin@example.com" required autofocus>
</div>
<div class="form-group">
<label for="loginPassword">Password</label>
<input type="password" id="loginPassword" placeholder="Enter password" required>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;justify-content:center;padding:12px;">
Sign In
</button>
</form>
<a href="/auth/sso/login?mode=web" id="ssoLoginBtn" class="btn btn-outline" style="display:none;width:100%;justify-content:center;margin-top:12px;">Sign in with Casdoor</a>
<p style="text-align:center;margin-top:16px;font-size:13px;color:var(--text-muted);">
<a href="#" id="showPasswordReset" style="color:var(--primary);">Reset password</a>
<span id="registerPrompt"> · No account? <a href="#" id="showRegister" style="color:var(--primary);">Register</a></span>
</p>
</div>
</div>
<!-- Registration modal on login page -->
<div id="registerOverlay" class="modal-overlay">
<div class="modal">
<h3>Create Account</h3>
<form id="registerForm">
<div class="form-group">
<label for="regName">Name</label>
<input type="text" id="regName" placeholder="Your name" required>
</div>
<div class="form-group">
<label for="regEmail">Email</label>
<input type="email" id="regEmail" placeholder="you@example.com" required>
</div>
<div class="form-group">
<label for="regPassword">Password</label>
<input type="password" id="regPassword" placeholder="Min 6 characters" required minlength="6">
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeRegister()">Cancel</button>
<button type="submit" class="btn btn-primary">Register & Sign In</button>
</div>
</form>
</div>
</div>
<!-- ============================================================
APP SHELL (hidden until authenticated)
============================================================ -->
<div id="appContainer" class="app-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="logo">🐦</div>
<h2>Shorebird</h2>
</div>
<nav class="sidebar-nav" id="sidebarNav">
<a href="#dashboard" class="active" data-page="dashboard">
<span class="nav-icon">📊</span> Dashboard
</a>
<a href="#apps" data-page="apps">
<span class="nav-icon">📱</span> Apps
</a>
<a href="#users" data-page="users">
<span class="nav-icon">👥</span> Users
</a>
<a href="#organizations" data-page="organizations">
<span class="nav-icon">🏢</span> Organizations
</a>
<a href="#settings" data-page="settings">
<span class="nav-icon">⚙️</span> Settings
</a>
</nav>
<div class="sidebar-footer" id="sidebarFooter">
Shorebird v1.0 · Self-Hosted
</div>
</aside>
<!-- Main Content Area -->
<main class="main-content">
<!-- Top Bar -->
<div class="top-bar">
<h2 id="pageTitle">Dashboard</h2>
<div class="user-info">
<span id="currentUserEmail"></span>
<button class="btn-sm" id="logoutBtn" title="Sign out">Sign Out</button>
</div>
</div>
<!-- Toast container -->
<div class="toast-container" id="toastContainer"></div>
<!-- ================================================
DASHBOARD PAGE
================================================ -->
<section id="page-dashboard" class="page-section active">
<div class="stats-grid" id="statsGrid">
<div class="stat-card primary"><div class="stat-value" id="statApps"></div><div class="stat-label">Apps</div></div>
<div class="stat-card success"><div class="stat-value" id="statPatches"></div><div class="stat-label">Patches</div></div>
<div class="stat-card warning"><div class="stat-value" id="statUsers"></div><div class="stat-label">Users</div></div>
<div class="stat-card"><div class="stat-value" id="statOrganizations"></div><div class="stat-label">Organizations</div></div>
</div>
<div class="card">
<div class="card-header"><h3>Recent Apps</h3></div>
<div class="card-body" id="recentAppsTable"></div>
</div>
</section>
<!-- ================================================
APPS PAGE
================================================ -->
<section id="page-apps" class="page-section">
<div class="card">
<div class="card-header">
<h3>All Applications</h3>
<button class="btn btn-primary" onclick="openCreateAppModal()">+ New App</button>
</div>
<div class="card-body" id="appsTable"></div>
</div>
</section>
<!-- ================================================
USERS PAGE
================================================ -->
<section id="page-users" class="page-section">
<div class="card">
<div class="card-header">
<h3>All Users</h3>
<button class="btn btn-primary" onclick="openCreateUserModal()">+ New User</button>
</div>
<div class="card-body" id="usersTable"></div>
</div>
</section>
<section id="page-organizations" class="page-section">
<div class="card">
<div class="card-header">
<h3>Organizations</h3>
<button class="btn btn-primary" onclick="openCreateOrgModal()">+ New Organization</button>
</div>
<div class="card-body" id="organizationsTable"></div>
</div>
<div class="card" style="margin-top:20px;">
<div class="card-header"><h3>Organization Members</h3></div>
<div class="card-body" id="organizationMembersTable"></div>
</div>
</section>
<section id="page-app-detail" class="page-section">
<div class="card">
<div class="card-header">
<h3 id="appDetailTitle">App</h3>
<button class="btn btn-outline" onclick="navigate('apps')">Back</button>
</div>
<div class="card-body" style="padding:24px;">
<div class="tab-bar">
<button class="active" data-app-tab="releases" onclick="showAppTab('releases')">Releases</button>
<button data-app-tab="insights" onclick="showAppTab('insights')">Insights</button>
<button data-app-tab="collaborators" onclick="showAppTab('collaborators')">Collaborators</button>
<button data-app-tab="tracks" onclick="showAppTab('tracks')">Tracks</button>
<button data-app-tab="settings" onclick="showAppTab('settings')">Settings</button>
</div>
<div id="appTab-releases" class="app-tab"></div>
<div id="appTab-insights" class="app-tab" style="display:none;"></div>
<div id="appTab-collaborators" class="app-tab" style="display:none;"></div>
<div id="appTab-tracks" class="app-tab" style="display:none;"></div>
<div id="appTab-settings" class="app-tab" style="display:none;"></div>
</div>
</div>
</section>
<section id="page-release-detail" class="page-section">
<div class="card">
<div class="card-header">
<h3 id="releaseDetailTitle">Release</h3>
<button class="btn btn-outline" onclick="openAppPage(state.currentApp?.app_id)">Back</button>
</div>
<div class="card-body" style="padding:24px;">
<div class="tab-bar">
<button class="active" data-release-tab="overview" onclick="showReleaseTab('overview')">Overview</button>
<button data-release-tab="insights" onclick="showReleaseTab('insights')">Insights</button>
<button data-release-tab="artifacts" onclick="showReleaseTab('artifacts')">Artifacts</button>
<button data-release-tab="settings" onclick="showReleaseTab('settings')">Settings</button>
</div>
<div id="releaseTab-overview" class="release-tab"></div>
<div id="releaseTab-insights" class="release-tab" style="display:none;"></div>
<div id="releaseTab-artifacts" class="release-tab" style="display:none;"></div>
<div id="releaseTab-settings" class="release-tab" style="display:none;"></div>
</div>
</div>
</section>
<!-- ================================================
SETTINGS PAGE
================================================ -->
<section id="page-settings" class="page-section">
<div class="card">
<div class="card-header"><h3>Personal Settings</h3></div>
<div class="card-body" style="padding:24px;">
<form id="changePasswordForm" style="max-width:520px;">
<div class="form-group">
<label for="currentPassword">Current Password</label>
<input type="password" id="currentPassword" required>
</div>
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" id="newPassword" required minlength="6">
</div>
<button type="submit" class="btn btn-primary">Change Password</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header"><h3>Server Configuration</h3></div>
<div class="card-body" style="padding:24px;">
<div class="info-grid" id="serverInfo"></div>
</div>
</div>
<div class="card" style="margin-top:20px;">
<div class="card-header"><h3>Admin Settings</h3></div>
<div class="card-body" style="padding:24px;">
<form id="adminSettingsForm" style="max-width:720px;">
<div class="form-group"><label><input type="checkbox" id="settingRegistrationEnabled" style="width:auto;"> Enable password registration</label></div>
<div class="form-group"><label><input type="checkbox" id="settingSsoRegistrationEnabled" style="width:auto;"> Enable SSO registration</label></div>
<div class="form-group"><label><input type="checkbox" id="settingSsoOnlyRegistration" style="width:auto;"> SSO-only registration</label></div>
<div class="form-group"><label for="casdoorEndpoint">Casdoor endpoint</label><input type="url" id="casdoorEndpoint" placeholder="https://casdoor.example.com"></div>
<div class="form-group"><label for="casdoorClientId">Casdoor app ID</label><input type="text" id="casdoorClientId"></div>
<div class="form-group"><label for="casdoorClientSecret">Casdoor app secret</label><input type="password" id="casdoorClientSecret" placeholder="Leave blank to keep current secret"></div>
<div class="form-group"><label for="casdoorOrganization">Casdoor organization</label><input type="text" id="casdoorOrganization" placeholder="built-in"></div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</div>
</div>
<div class="card" style="margin-top:20px;">
<div class="card-header"><h3>API Token</h3></div>
<div class="card-body" style="padding:24px;">
<p style="font-size:14px;color:var(--text-secondary);margin-bottom:12px;">
Use this token to authenticate the Shorebird CLI:
</p>
<div class="form-group">
<input type="text" id="apiTokenDisplay" readonly
style="font-family:var(--mono);font-size:13px;cursor:pointer;"
onclick="this.select();document.execCommand('copy')"
title="Click to copy">
</div>
<p class="form-hint">Click the token to copy. Set as <code>SHOREBIRD_TOKEN</code> env var.</p>
</div>
</div>
</section>
</main>
</div>
<!-- ============================================================
MODALS
============================================================ -->
<!-- Create App Modal -->
<div id="createAppOverlay" class="modal-overlay">
<div class="modal">
<h3>Create New App</h3>
<form id="createAppForm">
<div class="form-group">
<label for="appDisplayName">Display Name</label>
<input type="text" id="appDisplayName" placeholder="My Flutter App" required>
</div>
<div class="form-group">
<label for="appOrganizationId">Organization</label>
<select id="appOrganizationId"></select>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal('createAppOverlay')">Cancel</button>
<button type="submit" class="btn btn-primary">Create App</button>
</div>
</form>
</div>
</div>
<!-- Create User Modal -->
<div id="createUserOverlay" class="modal-overlay">
<div class="modal">
<h3>Create New User</h3>
<form id="createUserForm">
<div class="form-group">
<label for="newUserName">Name</label>
<input type="text" id="newUserName" placeholder="Full name" required>
</div>
<div class="form-group">
<label for="newUserEmail">Email</label>
<input type="email" id="newUserEmail" placeholder="user@example.com" required>
</div>
<div class="form-group">
<label for="newUserPassword">Password</label>
<input type="password" id="newUserPassword" placeholder="Min 6 characters" required minlength="6">
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal('createUserOverlay')">Cancel</button>
<button type="submit" class="btn btn-primary">Create User</button>
</div>
</form>
</div>
</div>
<!-- Confirm Delete Modal -->
<div id="confirmModal" class="modal-overlay">
<div class="modal">
<h3 id="confirmTitle">Confirm</h3>
<p id="confirmMessage" style="font-size:14px;color:var(--text-secondary);"></p>
<div class="modal-actions">
<button class="btn btn-outline" onclick="closeModal('confirmModal')">Cancel</button>
<button class="btn btn-danger" id="confirmBtn" onclick="executeConfirm()">Delete</button>
</div>
</div>
</div>
<div id="passwordResetOverlay" class="modal-overlay">
<div class="modal">
<h3>Reset Password</h3>
<form id="passwordResetForm">
<div class="form-group">
<label for="resetEmail">Email</label>
<input type="email" id="resetEmail" placeholder="you@example.com" required>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal('passwordResetOverlay')">Cancel</button>
<button type="submit" class="btn btn-primary">Send Reset Link</button>
</div>
</form>
</div>
</div>
<div id="forcePasswordOverlay" class="modal-overlay">
<div class="modal">
<h3>Change Default Password</h3>
<form id="forcePasswordForm">
<div class="form-group">
<label for="forceNewPassword">New Password</label>
<input type="password" id="forceNewPassword" required minlength="6">
</div>
<div class="modal-actions">
<button type="submit" class="btn btn-primary">Update Password</button>
</div>
</form>
</div>
</div>
<div id="createOrgOverlay" class="modal-overlay">
<div class="modal">
<h3>Create Organization</h3>
<form id="createOrgForm">
<div class="form-group">
<label for="orgName">Name</label>
<input type="text" id="orgName" placeholder="Team name" required>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal('createOrgOverlay')">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
<div id="addOrgUserOverlay" class="modal-overlay">
<div class="modal">
<h3>Add Organization User</h3>
<form id="addOrgUserForm">
<input type="hidden" id="orgUserOrgId">
<div class="form-group">
<label for="orgUserEmail">Email</label>
<input type="email" id="orgUserEmail" required>
</div>
<div class="form-group">
<label for="orgUserRole">Role</label>
<select id="orgUserRole"><option value="member">Member</option><option value="admin">Admin</option></select>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-outline" onclick="closeModal('addOrgUserOverlay')">Cancel</button>
<button type="submit" class="btn btn-primary">Add User</button>
</div>
</form>
</div>
</div>
<script src="/web/js/app.js"></script>
</body>
</html>