feat: TTLock — add Учетная запись + Пароль fields for OAuth

Per Sciener docs: OAuth requires TTHotel account credentials (Учетная запись + Пароль)
separate from developer app client_id/client_secret.

- Migration 055: add ttlock_username, ttlock_password columns
- Backend: store and pass new fields to agent
- UI: add input fields matching TTHotel PMS integration page
- Agent: getAccessToken() uses ttlockUsername/ttlockPassword when provided

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 11:46:30 +03:00
parent a7b59c4881
commit 8c215f2dcd
4 changed files with 108 additions and 58 deletions

View File

@@ -139,11 +139,13 @@ export function TTLockPage() {
const [success, setSuccess] = useState<string | null>(null)
// Форма настроек
const [isEnabled, setIsEnabled] = useState(false)
const [clientId, setClientId] = useState('')
const [clientSecret, setClientSecret] = useState('')
const [cardSectors, setCardSectors] = useState('1,2,3,4,5,6,7,8,9,10')
const [apiServer, setApiServer] = useState('https://euapi.ttlock.com')
const [isEnabled, setIsEnabled] = useState(false)
const [clientId, setClientId] = useState('')
const [clientSecret, setClientSecret] = useState('')
const [ttlockUsername, setTtlockUsername] = useState('')
const [ttlockPassword, setTtlockPassword] = useState('')
const [cardSectors, setCardSectors] = useState('1,2,3,4,5,6,7,8,9,10')
const [apiServer, setApiServer] = useState('https://euapi.ttlock.com')
// Per-workstation COM port state
const [wsComPorts, setWsComPorts] = useState<Record<string, string>>({})
@@ -242,6 +244,7 @@ export function TTLockPage() {
setConfig(cfg)
setIsEnabled(cfg.isEnabled)
setClientId(cfg.clientId)
setTtlockUsername(cfg.ttlockUsername || '')
setCardSectors(cfg.cardSectors || '1,2,3,4,5,6,7,8,9,10')
setApiServer(cfg.apiServer || 'https://euapi.ttlock.com')
setMappings(maps)
@@ -263,13 +266,16 @@ export function TTLockPage() {
try {
await api.ttlock.updateConfig(slug, {
isEnabled,
clientId: clientId.trim(),
clientSecret: clientSecret.trim() || undefined,
cardSectors: cardSectors.trim(),
apiServer: apiServer,
clientId: clientId.trim(),
clientSecret: clientSecret.trim() || undefined,
ttlock_username: ttlockUsername.trim() || undefined,
ttlock_password: ttlockPassword.trim() || undefined,
cardSectors: cardSectors.trim(),
apiServer: apiServer,
})
showSuccess('Настройки сохранены')
setClientSecret('')
setTtlockPassword('')
await load()
} catch {
setError('Ошибка сохранения')
@@ -410,6 +416,27 @@ export function TTLockPage() {
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="form-label">Учетная запись <span className="font-normal text-slate-400">(поле «Учетная запись» в TTHotel)</span></label>
<input
type="text"
value={ttlockUsername}
onChange={e => setTtlockUsername(e.target.value)}
placeholder="h_1754382851163"
className="input w-full font-mono text-sm"
/>
</div>
<div>
<label className="form-label">Пароль <span className="font-normal text-slate-400">(поле «Пароль» в TTHotel)</span></label>
<PasswordInput
value={ttlockPassword}
onChange={setTtlockPassword}
placeholder={ttlockUsername && !ttlockPassword ? '••••••• (не изменится)' : 'Пароль от учетной записи TTHotel'}
/>
</div>
</div>
{/* Сервер TTLock API */}
<div>
<label className="form-label">Сервер TTLock API</label>