mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Fix: App Pin & Downloads (Filesystem Changes)
This commit is contained in:
@@ -31,6 +31,35 @@
|
||||
let flareTtl = $state(settingsState.settings.flareSolverrSessionTtl ?? 15)
|
||||
let flareFallback = $state(settingsState.settings.flareSolverrAsResponseFallback ?? false)
|
||||
|
||||
let lockEnabled = $state(settingsState.settings.appLockEnabled ?? false)
|
||||
let lockPin = $state(settingsState.settings.appLockEnabled ? (settingsState.settings.appLockPin ?? '') : '')
|
||||
let lockPinVis = $state(false)
|
||||
let lockError = $state<string | null>(null)
|
||||
let lockSaved = $state(false)
|
||||
|
||||
function onLockToggle() {
|
||||
lockEnabled = !lockEnabled
|
||||
lockError = null
|
||||
lockSaved = false
|
||||
if (!lockEnabled) {
|
||||
lockPin = ''
|
||||
updateSettings({ appLockEnabled: false, appLockPin: '' })
|
||||
}
|
||||
}
|
||||
|
||||
function onLockPinInput() {
|
||||
lockPin = lockPin.replace(/\D/g, '')
|
||||
lockError = null
|
||||
lockSaved = false
|
||||
}
|
||||
|
||||
function saveLockPin() {
|
||||
if (lockPin.length < 4) { lockError = 'PIN must be at least 4 digits'; return }
|
||||
updateSettings({ appLockEnabled: true, appLockPin: lockPin })
|
||||
lockSaved = true
|
||||
setTimeout(() => lockSaved = false, 2000)
|
||||
}
|
||||
|
||||
function normalizeAuthMode(mode: string): 'NONE' | 'BASIC_AUTH' | 'UI_LOGIN' {
|
||||
if (mode === 'BASIC_AUTH' || mode === 'UI_LOGIN' || mode === 'NONE') return mode
|
||||
return 'NONE'
|
||||
@@ -283,6 +312,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="s-section">
|
||||
<p class="s-section-title">App Lock</p>
|
||||
<div class="s-section-body">
|
||||
<label class="s-row">
|
||||
<div class="s-row-info">
|
||||
<span class="s-label">Require PIN on launch</span>
|
||||
<span class="s-desc">Lock the app behind a numeric PIN when it opens</span>
|
||||
</div>
|
||||
<button role="switch" aria-checked={lockEnabled} aria-label="Enable app lock" class="s-toggle" class:on={lockEnabled}
|
||||
onclick={onLockToggle}><span class="s-toggle-thumb"></span></button>
|
||||
</label>
|
||||
{#if lockEnabled}
|
||||
{#if lockError}
|
||||
<div class="s-banner s-banner-error" style="margin: 0">{lockError}</div>
|
||||
{/if}
|
||||
<div class="s-row">
|
||||
<div class="s-row-info">
|
||||
<span class="s-label">PIN</span>
|
||||
<span class="s-desc">Minimum 4 digits</span>
|
||||
</div>
|
||||
<div class="s-pin-row">
|
||||
<div class="s-field-wrap">
|
||||
<input class="s-input" type={lockPinVis ? 'text' : 'password'} inputmode="numeric" pattern="\d*"
|
||||
bind:value={lockPin} oninput={onLockPinInput} placeholder="••••" autocomplete="off" spellcheck="false" maxlength="8" />
|
||||
<button class="s-eye-btn" onclick={() => lockPinVis = !lockPinVis} tabindex="-1" aria-label={lockPinVis ? 'Hide PIN' : 'Show PIN'}>{@html lockPinVis ? EyeClose : EyeOpen}</button>
|
||||
</div>
|
||||
<button class="s-btn s-btn-accent" onclick={saveLockPin} disabled={!lockPin}>
|
||||
{lockSaved ? 'Saved ✓' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="s-section">
|
||||
<p class="s-section-title">FlareSolverr</p>
|
||||
<div class="s-section-body">
|
||||
@@ -337,4 +401,5 @@
|
||||
.s-ghost-btn { display: inline-flex; align-items: center; gap: 5px; background: none; border: none; color: var(--text-faint); font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); cursor: pointer; padding: 2px 0; transition: color 0.15s; }
|
||||
.s-ghost-btn:hover:not(:disabled) { color: var(--color-error); }
|
||||
.s-ghost-btn:disabled { opacity: 0.35; cursor: default; }
|
||||
.s-pin-row { display: flex; align-items: center; gap: 8px; }
|
||||
</style>
|
||||
@@ -168,11 +168,11 @@
|
||||
if (!supportsFilesystem) return
|
||||
storageLoading = true; storageError = null
|
||||
try {
|
||||
const pathData = await gql<{ downloadsPath: string | null; localSourcePath: string | null }>(
|
||||
`{ downloadsPath localSourcePath }`
|
||||
const pathData = await gql<{ settings: { downloadsPath: string | null; localSourcePath: string | null } }>(
|
||||
`{ settings { downloadsPath localSourcePath } }`
|
||||
)
|
||||
const dl = pathData.downloadsPath ?? ''
|
||||
const loc = pathData.localSourcePath ?? ''
|
||||
const dl = pathData.settings.downloadsPath ?? ''
|
||||
const loc = pathData.settings.localSourcePath ?? ''
|
||||
downloadsPathInput = dl; localSourcePathInput = loc
|
||||
confirmedDownloadsPath = dl; confirmedLocalSourcePath = loc
|
||||
updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc })
|
||||
@@ -218,8 +218,8 @@
|
||||
if (dlErr || locErr) { pathsFieldError = { ...(dlErr ? { dl: dlErr } : {}), ...(locErr ? { loc: locErr } : {}) }; return }
|
||||
pathsSaving = true
|
||||
try {
|
||||
await gql(`mutation($path: String!) { setDownloadsPath(input: { location: $path }) { location } }`, { path: dl })
|
||||
if (loc) await gql(`mutation($path: String!) { setLocalSourcePath(input: { location: $path }) { location } }`, { path: loc })
|
||||
await gql(`mutation($path: String!) { setSettings(input: { settings: { downloadsPath: $path } }) { settings { downloadsPath } } }`, { path: dl })
|
||||
if (loc) await gql(`mutation($path: String!) { setSettings(input: { settings: { localSourcePath: $path } }) { settings { localSourcePath } } }`, { path: loc })
|
||||
updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc })
|
||||
if (supportsFilesystem && !isExternalServer) {
|
||||
const oldDl = confirmedDownloadsPath || defaultDownloadsPath
|
||||
|
||||
Reference in New Issue
Block a user