feat: TTLock API server selector (EU vs China) + pass apiServer to agent
- Add api_server column to hotel_ttlock_config (migration 053) - GET/PATCH config includes apiServer field - issue-card passes apiServer to agent command - Agent: WriteCardCmd.apiServer optional, all API calls use it - TTLockPage: radio buttons EU/China with hint about 404 error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1091,6 +1091,7 @@ export interface TTLockConfig {
|
||||
isEnabled: boolean
|
||||
clientId: string
|
||||
cardSectors: string
|
||||
apiServer: string
|
||||
}
|
||||
|
||||
export interface TTLockConfigUpdate {
|
||||
@@ -1098,6 +1099,7 @@ export interface TTLockConfigUpdate {
|
||||
clientId?: string
|
||||
clientSecret?: string
|
||||
cardSectors?: string
|
||||
apiServer?: string
|
||||
}
|
||||
|
||||
export interface RoomLockMapping {
|
||||
|
||||
@@ -142,6 +142,7 @@ export function TTLockPage() {
|
||||
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://euopen.ttlock.com')
|
||||
|
||||
// Per-workstation COM port state
|
||||
const [wsComPorts, setWsComPorts] = useState<Record<string, string>>({})
|
||||
@@ -241,6 +242,7 @@ export function TTLockPage() {
|
||||
setIsEnabled(cfg.isEnabled)
|
||||
setClientId(cfg.clientId)
|
||||
setCardSectors(cfg.cardSectors || '1,2,3,4,5,6,7,8,9,10')
|
||||
setApiServer(cfg.apiServer || 'https://euopen.ttlock.com')
|
||||
setMappings(maps)
|
||||
setRooms(roomList)
|
||||
setWorkstations(wsList)
|
||||
@@ -263,6 +265,7 @@ export function TTLockPage() {
|
||||
clientId: clientId.trim(),
|
||||
clientSecret: clientSecret.trim() || undefined,
|
||||
cardSectors: cardSectors.trim(),
|
||||
apiServer: apiServer,
|
||||
})
|
||||
showSuccess('Настройки сохранены')
|
||||
setClientSecret('')
|
||||
@@ -391,6 +394,41 @@ export function TTLockPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Сервер TTLock API */}
|
||||
<div>
|
||||
<label className="form-label">Сервер TTLock API</label>
|
||||
<div className="flex gap-3 mt-1">
|
||||
{[
|
||||
{ value: 'https://euopen.ttlock.com', label: 'EU', hint: 'euopen.ttlock.com' },
|
||||
{ value: 'https://open.ttlock.com', label: 'China', hint: 'open.ttlock.com' },
|
||||
].map(opt => (
|
||||
<label
|
||||
key={opt.value}
|
||||
className={cn(
|
||||
'flex items-center gap-2 px-4 py-2.5 rounded-lg border-2 cursor-pointer transition-colors select-none',
|
||||
apiServer === opt.value
|
||||
? 'border-brand-500 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300'
|
||||
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:border-slate-300',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="apiServer"
|
||||
value={opt.value}
|
||||
checked={apiServer === opt.value}
|
||||
onChange={() => setApiServer(opt.value)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span className="font-semibold text-sm">{opt.label}</span>
|
||||
<span className="text-xs font-mono opacity-60">{opt.hint}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-1.5">
|
||||
Если получаете ошибку 404 при выдаче карты — попробуйте другой сервер.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Секторы карты — визуальный пикер */}
|
||||
<div>
|
||||
<label className="form-label">Секторы карты</label>
|
||||
|
||||
Reference in New Issue
Block a user