import { Component, createSignal, Show, onMount } from 'solid-js'; import { showSuccess, showError } from '@/utils/toast'; import { copyToClipboard } from '@/utils/clipboard'; import { STORAGE_KEYS } from '@/constants'; import { SectionHeader } from '@/components/shared/SectionHeader'; export const FirstRunSetup: Component = () => { const [username, setUsername] = createSignal('admin'); const [password, setPassword] = createSignal(''); const [confirmPassword, setConfirmPassword] = createSignal(''); const [useCustomPassword, setUseCustomPassword] = createSignal(false); const [generatedPassword, setGeneratedPassword] = createSignal(''); const [, setApiToken] = createSignal(''); const [isSettingUp, setIsSettingUp] = createSignal(false); const [showCredentials, setShowCredentials] = createSignal(false); const [savedUsername, setSavedUsername] = createSignal(''); const [savedPassword, setSavedPassword] = createSignal(''); const [savedToken, setSavedToken] = createSignal(''); const [copied, setCopied] = createSignal<'password' | 'token' | null>(null); const [themeMode, setThemeMode] = createSignal<'system' | 'light' | 'dark'>('system'); const applyTheme = (mode: 'system' | 'light' | 'dark') => { if (mode === 'light') { document.documentElement.classList.remove('dark'); localStorage.setItem(STORAGE_KEYS.DARK_MODE, 'false'); } else if (mode === 'dark') { document.documentElement.classList.add('dark'); localStorage.setItem(STORAGE_KEYS.DARK_MODE, 'true'); } else { // System preference localStorage.removeItem(STORAGE_KEYS.DARK_MODE); if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } }; onMount(() => { // Check for saved theme preference const savedTheme = localStorage.getItem(STORAGE_KEYS.DARK_MODE); if (savedTheme === 'false') { setThemeMode('light'); document.documentElement.classList.remove('dark'); } else if (savedTheme === 'true') { setThemeMode('dark'); document.documentElement.classList.add('dark'); } else { // No saved preference - use system preference setThemeMode('system'); if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } }); const generatePassword = () => { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%'; let pass = ''; for (let i = 0; i < 16; i++) { pass += chars.charAt(Math.floor(Math.random() * chars.length)); } return pass; }; const generateToken = (): string => { // Generate 24 bytes (48 hex chars) to avoid hash detection issue const array = new Uint8Array(24); crypto.getRandomValues(array); return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(''); }; const handleSetup = async () => { // Validate custom password if used if (useCustomPassword()) { if (!password()) { showError('Please enter a password'); return; } if (password() !== confirmPassword()) { showError('Passwords do not match'); return; } if (password().length < 8) { showError('Password must be at least 8 characters'); return; } } setIsSettingUp(true); // Generate password if not custom const finalPassword = useCustomPassword() ? password() : generatePassword(); if (!useCustomPassword()) { setGeneratedPassword(finalPassword); } // Generate API token const token = generateToken(); setApiToken(token); try { const response = await fetch('/api/security/quick-setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username(), password: finalPassword, apiToken: token, }), }); if (!response.ok) { const error = await response.text(); throw new Error(error || 'Failed to setup security'); } const result = await response.json(); if (result.skipped) { // Shouldn't happen in first-run, but handle it window.location.reload(); return; } // Save credentials for display setSavedUsername(username()); setSavedPassword(useCustomPassword() ? password() : generatedPassword()); setSavedToken(token); // Show credentials setShowCredentials(true); showSuccess('Security configured successfully!'); } catch (error) { showError(`Failed to setup security: ${error}`); } finally { setIsSettingUp(false); } }; const handleCopy = async (type: 'password' | 'token') => { const value = type === 'password' ? savedPassword() : savedToken(); const success = await copyToClipboard(value); if (success) { setCopied(type); setTimeout(() => setCopied(null), 2000); } }; const downloadCredentials = () => { const credentials = `Pulse Security Credentials ======================== Generated: ${new Date().toISOString()} Web Interface Login: ------------------- URL: ${window.location.origin} Username: ${savedUsername()} Password: ${savedPassword()} API Access: ----------- API Token: ${savedToken()} Example API Usage: curl -H "X-API-Token: ${savedToken()}" ${window.location.origin}/api/state IMPORTANT: Keep these credentials secure! `; const blob = new Blob([credentials], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `pulse-credentials-${Date.now()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; return (
Let's set up your monitoring dashboard
A secure 16-character password will be generated for you. Make sure to save it when shown!
{themeMode() === 'system' ? 'Using your operating system theme preference' : `Using ${themeMode()} mode`}
Save your credentials now - they won't be shown again
{savedPassword()}
{savedToken()}
⚠️ Important
These credentials will never be shown again. Save them in a password manager now!