XIVLauncherRemoteOTP/static/index.html

232 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XIVLauncher Remote OTP</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="icon" type="image/png" href="/static/favicon.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<div class="logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<h1>XIVLauncher Remote OTP</h1>
</div>
<div class="header-actions">
<button id="help-btn" class="btn btn-icon" title="Firewall Help">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
</button>
<button id="logout-btn" class="btn btn-icon btn-logout" title="Logout" style="display: none;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
</button>
</div>
</header>
<nav id="main-nav" class="main-nav" style="display: none;">
<button class="nav-item active" data-tab="instances">Instances</button>
<button class="nav-item" id="nav-admin" data-tab="admin" style="display: none;">Admin</button>
<button class="nav-item" data-tab="profile">Profile</button>
</nav>
<main>
<div id="instances-section" class="tab-content active">
<div class="card instances-card">
<div class="instances-header">
<h2>Managed Instances</h2>
<div class="actions">
<button class="btn btn-primary btn-sm" id="show-add-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add
</button>
<button class="btn btn-icon" id="refresh-btn" title="Refresh">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
</button>
</div>
</div>
<div id="instances-list" class="instances-list">
<!-- Instances injected via JS -->
</div>
</div>
</div>
<div id="admin-section" class="tab-content">
<div class="card user-management-card">
<div class="instances-header">
<h2>User Management</h2>
<button class="btn btn-primary btn-sm" id="show-add-user-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add User
</button>
</div>
<div id="users-list" class="users-list">
<!-- Users injected via JS -->
</div>
</div>
</div>
<div id="profile-section" class="tab-content">
<div class="card profile-card">
<h2>My Profile</h2>
<p class="helper-text">Update your account details below.</p>
<form id="change-username-form" class="profile-form">
<div class="form-group">
<label for="profile-username">Username</label>
<input type="text" id="profile-username" placeholder="Enter new username" required>
</div>
<button type="submit" class="btn btn-primary">Update Username</button>
</form>
<div class="divider"></div>
<form id="change-password-form" class="profile-form">
<div class="form-group">
<label for="profile-new-password">Password</label>
<input type="password" id="profile-new-password" placeholder="Enter new password" required>
</div>
<button type="submit" class="btn btn-primary">Update Password</button>
</form>
<div id="profile-success" class="success-msg" style="display: none;">Profile updated successfully!</div>
</div>
</div>
</main>
</div>
<!-- Modals -->
<div id="add-instance-modal" class="modal">
<div class="modal-content card">
<div class="modal-header">
<h2>Add Instance</h2>
<button class="btn-close">&times;</button>
</div>
<form id="add-form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" placeholder="e.g. Steam Deck" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="ip">IP Address</label>
<input type="text" id="ip" placeholder="192.168.1.100" required>
</div>
<div class="form-group">
<label for="port">Port</label>
<input type="number" id="port" value="4646" required>
</div>
</div>
<div class="form-group">
<label for="secret">OTP Secret</label>
<input type="password" id="secret" placeholder="Base32 Secret or otpauth:// URL" required>
</div>
<button type="submit" class="btn btn-primary">Add Instance</button>
</form>
</div>
</div>
<div id="add-user-modal" class="modal">
<div class="modal-content card">
<div class="modal-header">
<h2>Add User</h2>
<button class="btn-close">&times;</button>
</div>
<form id="create-user-form" class="user-create-form">
<div class="form-group">
<label for="new-username">Username</label>
<input type="text" id="new-username" placeholder="Enter username" required>
</div>
<div class="form-group">
<label for="new-password">Password</label>
<input type="password" id="new-password" placeholder="Enter password" required>
</div>
<div class="form-group">
<label class="checkbox-container">
<input type="checkbox" id="new-is-admin">
<span class="checkbox-label">Grant Administrator Privileges</span>
</label>
</div>
<button type="submit" class="btn btn-primary">Create User</button>
</form>
</div>
</div>
<div id="help-modal" class="modal">
<div class="modal-content card">
<div class="modal-header">
<h2>Firewall Help</h2>
<button class="btn-close">&times;</button>
</div>
<p class="helper-text">Run these commands on the machine running XIVLauncher to allow incoming connections on the specified port.</p>
<div class="code-block">
<span class="code-label">UFW</span>
<div class="code-wrapper">
<code id="ufw-cmd">sudo ufw allow from localhost to any port 4646 proto tcp</code>
<button class="btn-copy" onclick="copyToClipboard('ufw-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
<div class="code-block">
<span class="code-label">iptables</span>
<div class="code-wrapper">
<code id="iptables-cmd">sudo iptables -I INPUT -p tcp -s localhost --dport 4646 -j ACCEPT</code>
<button class="btn-copy" onclick="copyToClipboard('iptables-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
<div class="code-block">
<span class="code-label">Windows (PowerShell)</span>
<div class="code-wrapper">
<code id="win-cmd">New-NetFirewallRule -DisplayName "XIVLauncher OTP" -Direction Inbound -RemoteAddress localhost -LocalPort 4646 -Protocol TCP -Action Allow</code>
<button class="btn-copy" onclick="copyToClipboard('win-cmd')" title="Copy to clipboard">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button>
</div>
</div>
</div>
</div>
<div id="login-overlay" class="login-overlay">
<div class="login-card">
<div class="logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<h2>Login</h2>
</div>
<form id="login-form">
<div class="form-group">
<label for="login-username">Username</label>
<input type="text" id="login-username" placeholder="Enter your username" required autocomplete="username">
</div>
<div class="form-group">
<label for="login-password">Password</label>
<input type="password" id="login-password" placeholder="Enter your password" required autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<div id="login-error" class="login-error" style="display: none;">Invalid username or password</div>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>