update library sync and client to support new hydrus api
This commit is contained in:
+15
-4
@@ -32,6 +32,17 @@ export type HydrusFileDetails = HydrusMediaInfo & {
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
function sanitizeApiKey(value?: string) {
|
||||
return (value || '').replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
function sanitizePort(value?: string | number) {
|
||||
if (value == null) return undefined
|
||||
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined
|
||||
const trimmed = value.trim()
|
||||
return trimmed ? trimmed : undefined
|
||||
}
|
||||
|
||||
export function makeId() {
|
||||
return Date.now().toString() + '-' + Math.random().toString(36).slice(2, 9)
|
||||
}
|
||||
@@ -54,10 +65,10 @@ export class HydrusClient {
|
||||
constructor(cfg: Partial<ServerConfig> = {}) {
|
||||
this.cfg = {
|
||||
id: (cfg.id as string) || makeId(),
|
||||
name: cfg.name || '',
|
||||
host: cfg.host || '',
|
||||
port: cfg.port,
|
||||
apiKey: cfg.apiKey,
|
||||
name: (cfg.name || '').trim(),
|
||||
host: (cfg.host || '').trim(),
|
||||
port: sanitizePort(cfg.port),
|
||||
apiKey: sanitizeApiKey(cfg.apiKey),
|
||||
ssl: !!cfg.ssl,
|
||||
forceApiKeyInQuery: !!cfg.forceApiKeyInQuery
|
||||
}
|
||||
|
||||
@@ -36,6 +36,56 @@ import { syncLibraryCache } from '../librarySync'
|
||||
import { APP_THEME_PRESETS, type AppThemeId } from '../themes'
|
||||
const DEFAULT_SERVER_FORM = { name: '', host: '', port: undefined, apiKey: '', ssl: false, forceApiKeyInQuery: false }
|
||||
|
||||
function normalizeServerForm(form: Omit<Server, 'id' | 'lastTest'>) {
|
||||
return {
|
||||
...form,
|
||||
name: (form.name || '').trim(),
|
||||
host: (form.host || '').trim(),
|
||||
port: typeof form.port === 'string' ? form.port.trim() || undefined : form.port,
|
||||
apiKey: (form.apiKey || '').replace(/\s+/g, ''),
|
||||
}
|
||||
}
|
||||
|
||||
function validateServerForm(form: Omit<Server, 'id' | 'lastTest'>) {
|
||||
const normalized = normalizeServerForm(form)
|
||||
|
||||
if (!normalized.host) {
|
||||
return { normalized, error: 'Host is required.' }
|
||||
}
|
||||
|
||||
try {
|
||||
let candidate = normalized.host
|
||||
if (!/^https?:\/\//i.test(candidate)) {
|
||||
candidate = `${normalized.ssl ? 'https' : 'http'}://${candidate}`
|
||||
}
|
||||
|
||||
const parsed = new URL(candidate)
|
||||
if (!parsed.hostname) {
|
||||
return { normalized, error: 'Host is invalid. Use an IP, hostname, or full URL.' }
|
||||
}
|
||||
} catch {
|
||||
return { normalized, error: 'Host is invalid. Use an IP, hostname, or full URL.' }
|
||||
}
|
||||
|
||||
if (normalized.port !== undefined) {
|
||||
const portText = String(normalized.port)
|
||||
if (!/^\d+$/.test(portText)) {
|
||||
return { normalized, error: 'Port must contain digits only.' }
|
||||
}
|
||||
|
||||
const portNumber = Number(portText)
|
||||
if (portNumber < 1 || portNumber > 65535) {
|
||||
return { normalized, error: 'Port must be between 1 and 65535.' }
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized.apiKey && !/^[a-f0-9]{64}$/i.test(normalized.apiKey)) {
|
||||
return { normalized, error: 'API key should be a 64-character hexadecimal key. Any pasted spaces or line breaks were removed automatically.' }
|
||||
}
|
||||
|
||||
return { normalized, error: null }
|
||||
}
|
||||
|
||||
type SettingsPageProps = {
|
||||
onClose?: () => void
|
||||
devOverlayEnabled: boolean
|
||||
@@ -139,6 +189,7 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
|
||||
const startAdd = () => {
|
||||
setEditing(null)
|
||||
setForm(DEFAULT_SERVER_FORM)
|
||||
setLastTest(null)
|
||||
}
|
||||
|
||||
const startEdit = (s: Server) => {
|
||||
@@ -148,10 +199,17 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const { normalized, error } = validateServerForm(form)
|
||||
if (error) {
|
||||
setLastTest(error)
|
||||
return
|
||||
}
|
||||
|
||||
setForm(normalized)
|
||||
if (editing) {
|
||||
updateServer(editing.id, form)
|
||||
updateServer(editing.id, normalized)
|
||||
} else {
|
||||
const srv = addServer(form)
|
||||
const srv = addServer(normalized)
|
||||
setActiveServerId(srv.id)
|
||||
}
|
||||
onClose && onClose()
|
||||
@@ -176,9 +234,16 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
|
||||
}
|
||||
|
||||
const handleTestForm = async () => {
|
||||
const { normalized, error } = validateServerForm(form)
|
||||
if (error) {
|
||||
setLastTest(error)
|
||||
return
|
||||
}
|
||||
|
||||
setForm(normalized)
|
||||
setTesting(true)
|
||||
try {
|
||||
const res = await testServerConfig(form)
|
||||
const res = await testServerConfig(normalized)
|
||||
setLastTest(`${res.message}`)
|
||||
} catch (e: any) {
|
||||
setLastTest(`Error: ${e?.message ?? String(e)}`)
|
||||
@@ -453,9 +518,9 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
|
||||
)}
|
||||
<Box sx={{ mt: 2, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField label="Name (optional)" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} fullWidth />
|
||||
<TextField label="Host or IP" placeholder="192.168.1.128 or hydrus.local" value={form.host} onChange={(e) => setForm({ ...form, host: e.target.value })} fullWidth />
|
||||
<TextField label="Port" placeholder="45869" value={form.port as any ?? ''} onChange={(e) => setForm({ ...form, port: e.target.value })} fullWidth />
|
||||
<TextField label="API Key (optional)" value={form.apiKey} onChange={(e) => setForm({ ...form, apiKey: e.target.value })} fullWidth />
|
||||
<TextField label="Host or URL" placeholder="192.168.1.128, hydrus.local, or http://192.168.1.128" value={form.host} onChange={(e) => setForm({ ...form, host: e.target.value })} fullWidth autoCapitalize="off" autoCorrect="off" spellCheck={false} />
|
||||
<TextField label="Port" placeholder="45869" value={form.port as any ?? ''} onChange={(e) => setForm({ ...form, port: e.target.value })} fullWidth inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }} />
|
||||
<TextField label="API Key (optional)" value={form.apiKey} onChange={(e) => setForm({ ...form, apiKey: e.target.value })} fullWidth autoCapitalize="off" autoCorrect="off" spellCheck={false} helperText="Hex API key. Pasted spaces or line breaks are ignored." />
|
||||
<FormControlLabel control={<Switch checked={!!form.ssl} onChange={(e) => setForm({ ...form, ssl: e.target.checked })} />} label="Use HTTPS (SSL)" />
|
||||
<FormControlLabel control={<Switch checked={!!form.forceApiKeyInQuery} onChange={(e) => setForm({ ...form, forceApiKeyInQuery: e.target.checked })} />} label="Send API key in query parameter" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user