Add a Server URL field to the admin-account step of the setup wizard so
the public base URL (used for device sync, OPDS feed, and API endpoints)
is configured up front instead of requiring a later visit to admin
settings.
- setup.templ: base URL input on the admin step plus a summary entry
- setup.ts: default baseUrl to window.location.origin and persist it via
PUT /system/config ({ base_url }) right after the admin account is
created, with a non-fatal warning if the save fails
41 lines
17 KiB
Go
41 lines
17 KiB
Go
// Code generated by templ - DO NOT EDIT.
|
||
|
||
// templ: version: v0.3.1020
|
||
package templates
|
||
|
||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||
|
||
import "github.com/a-h/templ"
|
||
import templruntime "github.com/a-h/templ/runtime"
|
||
|
||
func Setup() templ.Component {
|
||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||
return templ_7745c5c3_CtxErr
|
||
}
|
||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||
if !templ_7745c5c3_IsBuffer {
|
||
defer func() {
|
||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||
if templ_7745c5c3_Err == nil {
|
||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||
}
|
||
}()
|
||
}
|
||
ctx = templ.InitializeContext(ctx)
|
||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||
if templ_7745c5c3_Var1 == nil {
|
||
templ_7745c5c3_Var1 = templ.NopComponent
|
||
}
|
||
ctx = templ.ClearChildren(ctx)
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Setup - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/main.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-tokyo-night min-h-screen\" x-data=\"setupWizard\" x-init=\"initSetup()\"><div class=\"container mx-auto px-4 py-8 max-w-2xl\"><div class=\"text-center mb-8\"><h1 class=\"text-4xl font-bold mb-2\" style=\"color: var(--text-primary)\">Welcome to Bookhoard</h1><p class=\"text-lg\" style=\"color: var(--text-secondary)\">Let's get your library set up.</p></div><!-- Step indicator --><div class=\"flex items-center justify-center mb-8 gap-2\"><template x-for=\"n in 4\" :key=\"n\"><div class=\"flex items-center gap-2\"><div class=\"w-10 h-10 rounded-full flex items-center justify-center font-bold text-sm transition-all\" :class=\"currentStep >= n ? 'bg-[var(--accent)] text-[var(--bg-primary)]' : 'bg-[var(--bg-secondary)] text-[var(--text-secondary)] border border-[var(--border)]'\"><span x-text=\"currentStep > n ? '✓' : n\"></span></div><div x-text=\"['Admin', 'Libraries', 'Folders', 'Scan'][n-1]\" class=\"text-sm hidden sm:inline\" :style=\"currentStep >= n ? 'color: var(--text-primary)' : 'color: var(--text-secondary)'\"></div><div x-show=\"n < 4\" class=\"w-8 h-0.5 mx-1\" :class=\"currentStep > n ? 'bg-[var(--accent)]' : 'bg-[var(--border)]'\"></div></div></template></div><!-- Error display --><div x-show=\"errorMsg\" class=\"mb-4 p-4 rounded-lg border border-red-500/50 bg-red-500/10 text-red-400 text-sm\"><span x-text=\"errorMsg\"></span></div><!-- ==================== STEP 1: Create Admin ==================== --><div x-show=\"currentStep === 1\" class=\"card p-6 rounded-lg shadow-md border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h2 class=\"text-2xl font-bold mb-2\" style=\"color: var(--text-primary)\">Create Administrator Account</h2><p class=\"mb-6\" style=\"color: var(--text-secondary)\">This first account will have full admin privileges.</p><form @submit.prevent=\"submitAdmin()\"><div class=\"mb-6 p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Server URL (Base URL)</label> <input type=\"url\" x-model=\"admin.baseUrl\" placeholder=\"https://books.example.com\" class=\"w-full px-3 py-2 border rounded mt-2\" style=\"background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)\" required><p class=\"text-sm mt-2\" style=\"color: var(--text-secondary)\">The public URL of your Bookhoard instance. Used for device sync, OPDS feed, and API endpoints.</p></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Email</label> <input type=\"email\" id=\"email\" x-model=\"admin.email\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\" required></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Username</label> <input type=\"text\" x-model=\"admin.username\" id=\"username\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\" required></div><div class=\"grid grid-cols-2 gap-4 mb-4\"><div><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">First Name</label> <input type=\"text\" x-model=\"admin.firstName\" placeholder=\"Optional\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\"></div><div><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Last Name</label> <input type=\"text\" x-model=\"admin.lastName\" placeholder=\"Optional\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\"></div></div><div id=\"password-requirements\" class=\"mb-4 p-4 rounded-lg border text-sm\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><p class=\"font-semibold mb-2\" style=\"color: var(--text-primary)\">Password Requirements:</p><ul class=\"space-y-1\" style=\"color: var(--text-secondary)\"><li id=\"req-length\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> At least 8 characters</li><li id=\"req-upper\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One uppercase letter</li><li id=\"req-lower\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One lowercase letter</li><li id=\"req-number\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One number</li><li id=\"req-special\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> One special character</li><li id=\"req-match\" class=\"flex items-center gap-2\"><span class=\"requirement-icon\">○</span> Passwords match</li></ul></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Password</label> <input type=\"password\" x-model=\"admin.password\" id=\"password\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\" required></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Confirm Password</label> <input type=\"password\" x-model=\"admin.confirmPassword\" id=\"confirm-password\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\" required></div><button type=\"submit\" id=\"register-btn\" class=\"btn-primary w-full py-2 rounded opacity-50 cursor-not-allowed\" :disabled=\"loading\" x-text=\"loading ? 'Creating...' : 'Create Admin Account'\"></button></form></div><!-- ==================== STEP 2: Create Libraries ==================== --><div x-show=\"currentStep === 2\" class=\"card p-6 rounded-lg shadow-md border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h2 class=\"text-2xl font-bold mb-2\" style=\"color: var(--text-primary)\">Create Libraries</h2><p class=\"mb-6\" style=\"color: var(--text-secondary)\">Add one or more media libraries. You can always add more later.</p><!-- Created libraries list --><div x-show=\"libraries.length > 0\" class=\"mb-6 space-y-2\"><template x-for=\"(lib, idx) in libraries\" :key=\"lib.id\"><div class=\"p-3 rounded-lg border flex items-center justify-between\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><div class=\"flex items-center gap-3\"><span class=\"text-xl\" x-text=\"lib.type === 'ebooks' ? '📚' : lib.type === 'comics' ? '📖' : '🗾'\"></span><div><p class=\"font-medium\" style=\"color: var(--text-primary)\" x-text=\"lib.name\"></p><p class=\"text-xs capitalize\" style=\"color: var(--text-secondary)\" x-text=\"lib.type\"></p></div></div><button type=\"button\" @click=\"removeLibrary(idx)\" class=\"text-xs text-red-500 hover:underline\">Remove</button></div></template></div><!-- Add library form --><form @submit.prevent=\"addLibrary()\" class=\"space-y-4 p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><div><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Library Name</label> <input type=\"text\" x-model=\"newLibrary.name\" placeholder=\"My Ebook Library\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)\" required></div><div><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Library Type</label> <select x-model=\"newLibrary.type\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)\"><option value=\"ebooks\">📚 Ebooks</option> <option value=\"comics\">📖 Comics</option> <option value=\"manga\">🗾 Manga</option></select></div><div><label class=\"block text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Description (optional)</label> <input type=\"text\" x-model=\"newLibrary.description\" placeholder=\"Optional\" class=\"w-full px-3 py-2 border rounded\" style=\"background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)\"></div><button type=\"submit\" class=\"btn-secondary w-full py-2 rounded\" :disabled=\"loading || !newLibrary.name.trim()\" x-text=\"loading ? 'Creating...' : '+ Add This Library'\"></button></form><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 1\" class=\"btn-secondary px-4 py-2 rounded\">Back</button> <button type=\"button\" @click=\"currentStep = 3\" class=\"btn-primary px-6 py-2 rounded\"><span x-text=\"libraries.length > 0 ? 'Next: Configure Folders' : 'Skip – Continue'\"></span></button></div></div><!-- ==================== STEP 3: Configure Folders ==================== --><div x-show=\"currentStep === 3\" class=\"card p-6 rounded-lg shadow-md border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h2 class=\"text-2xl font-bold mb-2\" style=\"color: var(--text-primary)\">Configure Library Folders</h2><p class=\"mb-6\" style=\"color: var(--text-secondary)\">Add the filesystem folders where your media files are stored.</p><div x-show=\"libraries.length === 0\" class=\"text-center py-8\" style=\"color: var(--text-secondary)\"><p>No libraries created. You can add folders later from the admin panel.</p></div><div class=\"space-y-6\"><template x-for=\"lib in libraries\" :key=\"lib.id\"><div class=\"p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><div class=\"flex items-center gap-2 mb-3\"><span class=\"text-xl\" x-text=\"lib.type === 'ebooks' ? '📚' : lib.type === 'comics' ? '📖' : '🗾'\"></span><h4 class=\"font-semibold\" style=\"color: var(--text-primary)\" x-text=\"lib.name\"></h4></div><!-- Existing folders for this library --><div class=\"space-y-1 mb-3\" x-show=\"lib.folders && lib.folders.length > 0\"><template x-for=\"(folder, fIdx) in (lib.folders || [])\" :key=\"folder\"><div class=\"flex items-center justify-between p-2 rounded text-sm\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><span style=\"color: var(--text-primary)\" x-text=\"folder\"></span> <button type=\"button\" @click=\"removeFolder(lib.id, folder, fIdx)\" class=\"text-xs text-red-500 hover:underline\">Remove</button></div></template></div><!-- Add folder --><div class=\"flex gap-2\"><input type=\"text\" :placeholder=\"'/path/to/' + lib.type\" :id=\"'folderInput-' + lib.id\" class=\"flex-1 px-3 py-2 border rounded text-sm\" style=\"background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)\"> <button type=\"button\" @click=\"showFolderBrowser('folderInput-' + lib.id)\" class=\"btn-secondary px-3 py-2 text-xs rounded\">Browse</button> <button type=\"button\" @click=\"addFolderToLibrary(lib)\" class=\"btn-primary px-3 py-2 text-xs rounded\">Add</button></div></div></template></div><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 2\" class=\"btn-secondary px-4 py-2 rounded\">Back</button> <button type=\"button\" @click=\"currentStep = 4\" class=\"btn-primary px-6 py-2 rounded\">Next: Scan</button></div></div><!-- ==================== STEP 4: Scan & Finish ==================== --><div x-show=\"currentStep === 4\" class=\"card p-6 rounded-lg shadow-md border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h2 class=\"text-2xl font-bold mb-2\" style=\"color: var(--text-primary)\">Scan & Finish</h2><p class=\"mb-6\" style=\"color: var(--text-secondary)\">Review your configuration and start the initial scan.</p><!-- Summary --><div class=\"space-y-3 mb-6\"><div class=\"p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><p class=\"text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Server URL</p><p style=\"color: var(--text-primary)\" x-text=\"admin.baseUrl\"></p></div><div class=\"p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><p class=\"text-sm font-medium mb-1\" style=\"color: var(--text-secondary)\">Administrator</p><p style=\"color: var(--text-primary)\" x-text=\"admin.username + ' (' + admin.email + ')'\"></p></div><div class=\"p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><p class=\"text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Libraries (<span x-text=\"libraries.length\"></span>)</p><template x-for=\"lib in libraries\" :key=\"lib.id\"><div class=\"flex items-start justify-between text-sm py-1\"><div><span style=\"color: var(--text-primary)\" x-text=\"lib.name\"></span> <span class=\"ml-2 text-xs px-2 py-0.5 rounded\" style=\"background-color: var(--accent); color: var(--bg-primary)\" x-text=\"lib.type\"></span></div><div class=\"text-right\" style=\"color: var(--text-secondary)\"><span x-text=\"(lib.folders || []).length + ' folder(s)'\"></span></div></div></template></div></div><!-- Scan progress --><div x-show=\"scanStarted\" class=\"mb-6 space-y-3\"><h3 class=\"font-semibold\" style=\"color: var(--text-primary)\">Scan Progress</h3><div id=\"library-progress-list\" class=\"space-y-3\"><template x-for=\"job in scanJobs\" :key=\"job.jobId\"><div class=\"p-3 rounded border\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"><div class=\"flex justify-between items-center mb-2\"><span class=\"font-medium\" style=\"color: var(--text-primary)\" x-text=\"job.libraryName\"></span> <span class=\"text-sm\" style=\"color: var(--text-secondary)\" x-text=\"job.statusText\"></span></div><div class=\"w-full rounded-full h-2\" style=\"background-color: var(--bg-secondary)\"><div class=\"h-2 rounded-full transition-all duration-500\" :style=\"'width: ' + job.progress + '%; background-color: var(--accent);'\"></div></div></div></template></div><div x-show=\"scanComplete\" class=\"p-4 rounded-lg border border-green-500/50 bg-green-500/10 text-green-400 text-sm\">Initial scan complete! Your libraries are ready to use.</div></div><div class=\"flex justify-between mt-6\"><button type=\"button\" @click=\"currentStep = 3\" class=\"btn-secondary px-4 py-2 rounded\" :disabled=\"loading\">Back</button><div class=\"flex gap-3\"><button x-show=\"!scanStarted\" type=\"button\" @click=\"startScan()\" class=\"btn-primary px-6 py-2 rounded\" :disabled=\"loading\">Start Scan</button> <button x-show=\"scanStarted && !scanComplete\" type=\"button\" @click=\"finishSetup()\" class=\"btn-secondary px-6 py-2 rounded\" :disabled=\"loading\">Skip to Dashboard</button> <button x-show=\"scanComplete\" type=\"button\" @click=\"finishSetup()\" class=\"btn-primary px-6 py-2 rounded\" :disabled=\"loading\" x-text=\"loading ? 'Finishing...' : 'Go to Dashboard'\"></button></div></div></div></div><!-- Folder Browser Modal (reused from admin library) --><div id=\"folder-browser-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7)\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><div class=\"flex justify-between items-center mb-4\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Browse Folders</h2><button type=\"button\" @click=\"hideFolderBrowser()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><div id=\"folder-browser-content\"><!-- Directory listings rendered by Alpine --></div></div></div></body></html>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
var _ = templruntime.GeneratedTemplate
|