/
/
/
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Music Assistant - Create Admin Account</title>
7 <link rel="stylesheet" href="resources/common.css">
8 <style>
9 body {
10 min-height: 100vh;
11 display: flex;
12 justify-content: center;
13 align-items: center;
14 padding: 20px;
15 }
16
17 .setup-container {
18 background: var(--panel);
19 border-radius: 16px;
20 box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12),
21 0 0 0 1px var(--border);
22 padding: 48px 40px;
23 width: 100%;
24 max-width: 520px;
25 }
26
27 .logo {
28 text-align: center;
29 margin-bottom: 36px;
30 }
31
32 .logo-icon {
33 width: 72px;
34 height: 72px;
35 margin: 0 auto 16px;
36 }
37
38 .logo-icon img {
39 width: 100%;
40 height: 100%;
41 object-fit: contain;
42 }
43
44 .logo h1 {
45 color: var(--fg);
46 font-size: 24px;
47 font-weight: 600;
48 letter-spacing: -0.5px;
49 margin-bottom: 6px;
50 }
51
52 .logo p {
53 color: var(--text-tertiary);
54 font-size: 14px;
55 font-weight: 400;
56 }
57
58 .header {
59 margin-bottom: 24px;
60 }
61
62 .header h2 {
63 color: var(--fg);
64 font-size: 20px;
65 font-weight: 600;
66 margin-bottom: 8px;
67 }
68
69 .header p {
70 color: var(--text-secondary);
71 font-size: 14px;
72 line-height: 1.6;
73 }
74
75 .password-requirements {
76 margin-top: 8px;
77 font-size: 12px;
78 color: var(--text-tertiary);
79 }
80
81 .form-actions {
82 margin-top: 24px;
83 }
84
85 .btn {
86 width: 100%;
87 padding: 15px;
88 border: none;
89 border-radius: 10px;
90 font-size: 15px;
91 font-weight: 600;
92 cursor: pointer;
93 transition: all 0.2s ease;
94 letter-spacing: 0.3px;
95 }
96
97 .btn-primary {
98 background: var(--primary);
99 color: white;
100 }
101
102 .btn-primary:hover {
103 filter: brightness(1.1);
104 box-shadow: 0 8px 24px var(--primary-glow);
105 transform: translateY(-1px);
106 }
107
108 .btn-primary:active {
109 transform: translateY(0);
110 filter: brightness(0.95);
111 }
112
113 .btn-primary:disabled {
114 opacity: 0.5;
115 cursor: not-allowed;
116 transform: none;
117 box-shadow: none;
118 filter: none;
119 }
120
121 </style>
122</head>
123<body>
124 <div class="setup-container">
125 <div class="logo">
126 <div class="logo-icon">
127 <img src="logo.png" alt="Music Assistant">
128 </div>
129 <h1>Music Assistant</h1>
130 <p>Create Admin Account</p>
131 </div>
132
133 <div class="header">
134 <h2>Welcome!</h2>
135 <p>Create an administrator account to get started with Music Assistant.</p>
136 </div>
137
138 <div class="error-message" id="errorMessage"></div>
139
140 <form id="setupForm">
141 <div class="form-group">
142 <label for="username">Username</label>
143 <input
144 type="text"
145 id="username"
146 name="username"
147 required
148 autocomplete="username"
149 placeholder="Enter your username"
150 minlength="2"
151 >
152 </div>
153
154 <div class="form-group">
155 <label for="password">Password</label>
156 <input
157 type="password"
158 id="password"
159 name="password"
160 required
161 autocomplete="new-password"
162 placeholder="Enter a secure password"
163 minlength="8"
164 >
165 <div class="password-requirements">
166 Minimum 8 characters
167 </div>
168 </div>
169
170 <div class="form-group">
171 <label for="confirmPassword">Confirm Password</label>
172 <input
173 type="password"
174 id="confirmPassword"
175 name="confirmPassword"
176 required
177 autocomplete="new-password"
178 placeholder="Re-enter your password"
179 >
180 </div>
181
182 <div class="form-actions">
183 <button type="submit" class="btn btn-primary" id="createAccountBtn">Create Account</button>
184 </div>
185 </form>
186
187 </div>
188
189 <script>
190 const urlParams = new URLSearchParams(window.location.search);
191 const deviceName = urlParams.get('device_name');
192 const returnUrl = urlParams.get('return_url');
193
194 function showError(message) {
195 const errorMessage = document.getElementById('errorMessage');
196 errorMessage.textContent = message;
197 errorMessage.classList.add('show');
198 }
199
200 function hideError() {
201 const errorMessage = document.getElementById('errorMessage');
202 errorMessage.classList.remove('show');
203 }
204
205 function redirectWithToken(token, redirectTo) {
206 // Follow the server-provided target; fall back to a local redirect otherwise.
207 if (redirectTo) {
208 window.location.href = redirectTo;
209 } else {
210 window.location.href = `./?code=${encodeURIComponent(token)}&onboard=true`;
211 }
212 }
213
214 document.getElementById('setupForm').addEventListener('submit', async (e) => {
215 e.preventDefault();
216 hideError();
217
218 const username = document.getElementById('username').value.trim();
219 const password = document.getElementById('password').value;
220 const confirmPassword = document.getElementById('confirmPassword').value;
221
222 if (username.length < 2) {
223 showError('Username must be at least 2 characters long');
224 return;
225 }
226
227 if (password.length < 8) {
228 showError('Password must be at least 8 characters long');
229 return;
230 }
231
232 if (password !== confirmPassword) {
233 showError('Passwords do not match');
234 return;
235 }
236
237 const submitBtn = document.getElementById('createAccountBtn');
238 submitBtn.disabled = true;
239 submitBtn.textContent = 'Creating Account...';
240
241 try {
242 const requestBody = {
243 username: username,
244 password: password,
245 };
246
247 if (deviceName) {
248 requestBody.device_name = deviceName;
249 }
250
251 if (returnUrl) {
252 requestBody.return_url = returnUrl;
253 }
254
255 const response = await fetch('setup', {
256 method: 'POST',
257 headers: {
258 'Content-Type': 'application/json',
259 },
260 body: JSON.stringify(requestBody),
261 });
262
263 const data = await response.json();
264
265 if (response.ok && data.success) {
266 redirectWithToken(data.token, data.redirect_to);
267 } else {
268 submitBtn.disabled = false;
269 submitBtn.textContent = 'Create Account';
270 showError(data.error || 'Setup failed. Please try again.');
271 }
272 } catch (error) {
273 submitBtn.disabled = false;
274 submitBtn.textContent = 'Create Account';
275 showError('Network error. Please check your connection and try again.');
276 console.error('Setup error:', error);
277 }
278 });
279
280 document.querySelectorAll('input').forEach(input => {
281 input.addEventListener('input', hideError);
282 });
283 </script>
284</body>
285</html>
286