Add TravelLine pull integration for NetUP: capture endpoint + tl_token
- New /travelline route: accepts all NetUP polls, logs them to in-memory ring buffer (GET /travelline/_log to inspect), responds with active reservations in a generic PMS format - Migration 007: adds tl_token column to netup_settings - PATCH netup/settings now saves tl_token; GET returns it - TvWelcomePage: new TravelLine section with read-only API URL + copy button and token input field Setup in NetUP: Integration type = TravelLine, API URL = https://api.hotelsync.ru/travelline, Token = <tl_token> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Tv2, Plug, Map, CheckCircle2, XCircle, Loader2, Save, Eye, EyeOff } from 'lucide-react'
|
||||
import { Tv2, Plug, Map, CheckCircle2, XCircle, Loader2, Save, Eye, EyeOff, Copy, Link2 } from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { api } from '../lib/api'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
@@ -30,7 +30,9 @@ export function TvWelcomePage() {
|
||||
const [password, setPassword] = useState('')
|
||||
const [language, setLanguage] = useState('ru_RU')
|
||||
const [enabled, setEnabled] = useState(true)
|
||||
const [tlToken, setTlToken] = useState('')
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
// Room mappings
|
||||
const [rooms, setRooms] = useState<{ id: string; number: string; type: string; netupRoomNumber: string }[]>([])
|
||||
@@ -46,6 +48,7 @@ export function TvWelcomePage() {
|
||||
setUsername(s.username)
|
||||
setLanguage(s.defaultLanguage)
|
||||
setEnabled(s.enabled)
|
||||
setTlToken(s.tlToken ?? '')
|
||||
setSettingsLoaded(true)
|
||||
})
|
||||
.catch(() => setSettingsLoaded(true))
|
||||
@@ -69,6 +72,7 @@ export function TvWelcomePage() {
|
||||
password: password || undefined,
|
||||
default_language: language,
|
||||
enabled,
|
||||
tl_token: tlToken,
|
||||
})
|
||||
setPassword('')
|
||||
} finally {
|
||||
@@ -86,6 +90,7 @@ export function TvWelcomePage() {
|
||||
password: password || undefined,
|
||||
default_language: language,
|
||||
enabled,
|
||||
tl_token: tlToken,
|
||||
})
|
||||
const res = await api.netup.testConnection(slug)
|
||||
setTestResult({ ok: true, msg: res.message })
|
||||
@@ -244,6 +249,65 @@ export function TvWelcomePage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr className="border-slate-100 dark:border-slate-700" />
|
||||
|
||||
{/* TravelLine / Pull integration */}
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<p className="font-medium text-slate-900 dark:text-slate-100 text-sm">
|
||||
Интеграция через TravelLine (pull-режим)
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
||||
Альтернативный метод: NetUP сам опрашивает наш сервер. Настройте в NetUP тип интеграции «TravelLine» и укажите адрес ниже.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API URL (read-only) */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 dark:text-slate-300 flex items-center gap-1.5">
|
||||
<Link2 size={13} />
|
||||
API URL для NetUP
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value="https://api.hotelsync.ru/travelline"
|
||||
className="input w-full font-mono text-xs bg-slate-50 dark:bg-slate-700/50 text-slate-500 dark:text-slate-400"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText('https://api.hotelsync.ru/travelline')
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}}
|
||||
className="btn-secondary px-3 shrink-0 flex items-center gap-1.5"
|
||||
>
|
||||
{copied ? <CheckCircle2 size={13} className="text-emerald-500" /> : <Copy size={13} />}
|
||||
{copied ? 'Скопировано' : 'Копировать'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Token */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
Токен авторизации
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tlToken}
|
||||
onChange={e => setTlToken(e.target.value)}
|
||||
placeholder="Придумайте любой секретный токен"
|
||||
className="input w-full font-mono text-sm"
|
||||
/>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
Введите этот же токен в поле «Токен» настроек TravelLine в NetUP
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Test result */}
|
||||
{testResult && (
|
||||
<div className={cn(
|
||||
|
||||
Reference in New Issue
Block a user