feat: NetUP via agent — proxy HTTP requests through Windows workstation
- Migration 044: add connection_type + agent_workstation_id to netup_settings - Backend: netupReqViaCfg routes requests via agent WebSocket when connection_type=agent - Agent: add netup_request command handler (proxies HTTP to local NetUP server) - TvWelcomePage: add connection mode toggle (direct / via agent) + workstation selector - Hint changes: URL placeholder shows local IP when agent mode selected Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Tv2, Plug, Map, Activity, CheckCircle2, XCircle, Loader2, Save, Eye, EyeOff, RefreshCw, Trash2 } from 'lucide-react'
|
||||
import { Tv2, Plug, Map, Activity, CheckCircle2, XCircle, Loader2, Save, Eye, EyeOff, RefreshCw, Trash2, Wifi, Globe } from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { api } from '../lib/api'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import type { Workstation } from '../lib/api'
|
||||
|
||||
type Tab = 'connection' | 'rooms' | 'log'
|
||||
|
||||
@@ -38,11 +39,14 @@ export function TvWelcomePage() {
|
||||
const [showPass, setShowPass] = useState(false)
|
||||
|
||||
// Connection settings
|
||||
const [serverUrl, setServerUrl] = useState('')
|
||||
const [username, setUsername] = useState('admin')
|
||||
const [password, setPassword] = useState('')
|
||||
const [language, setLanguage] = useState('ru_RU')
|
||||
const [enabled, setEnabled] = useState(true)
|
||||
const [serverUrl, setServerUrl] = useState('')
|
||||
const [username, setUsername] = useState('admin')
|
||||
const [password, setPassword] = useState('')
|
||||
const [language, setLanguage] = useState('ru_RU')
|
||||
const [enabled, setEnabled] = useState(true)
|
||||
const [connectionType, setConnectionType] = useState<'direct' | 'agent'>('direct')
|
||||
const [agentWorkstationId, setAgentWorkstationId] = useState<string | null>(null)
|
||||
const [workstations, setWorkstations] = useState<Workstation[]>([])
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false)
|
||||
|
||||
// Room mappings
|
||||
@@ -57,7 +61,7 @@ export function TvWelcomePage() {
|
||||
const [testingCheckin, setTestingCheckin] = useState(false)
|
||||
const [testCheckinResult, setTestCheckinResult] = useState<{ ok: boolean; msg: string } | null>(null)
|
||||
|
||||
// Load settings
|
||||
// Load settings + workstations
|
||||
useEffect(() => {
|
||||
if (!slug) return
|
||||
api.netup.getSettings(slug)
|
||||
@@ -66,9 +70,12 @@ export function TvWelcomePage() {
|
||||
setUsername(s.username)
|
||||
setLanguage(s.defaultLanguage)
|
||||
setEnabled(s.enabled)
|
||||
setConnectionType((s.connectionType as 'direct' | 'agent') ?? 'direct')
|
||||
setAgentWorkstationId(s.agentWorkstationId ?? null)
|
||||
setSettingsLoaded(true)
|
||||
})
|
||||
.catch(() => setSettingsLoaded(true))
|
||||
api.workstations.list(slug).then(setWorkstations).catch(() => {})
|
||||
}, [slug])
|
||||
|
||||
// Load room mappings when tab switches (also needed on 'log' for test button)
|
||||
@@ -102,6 +109,8 @@ export function TvWelcomePage() {
|
||||
password: password || undefined,
|
||||
default_language: language, enabled,
|
||||
tl_token: '',
|
||||
connection_type: connectionType,
|
||||
agent_workstation_id: connectionType === 'agent' ? agentWorkstationId : null,
|
||||
})
|
||||
setPassword('')
|
||||
} finally {
|
||||
@@ -118,6 +127,8 @@ export function TvWelcomePage() {
|
||||
password: password || undefined,
|
||||
default_language: language, enabled,
|
||||
tl_token: '',
|
||||
connection_type: connectionType,
|
||||
agent_workstation_id: connectionType === 'agent' ? agentWorkstationId : null,
|
||||
})
|
||||
const res = await api.netup.testConnection(slug)
|
||||
setTestResult({ ok: true, msg: res.message })
|
||||
@@ -226,16 +237,71 @@ export function TvWelcomePage() {
|
||||
|
||||
<hr className="border-slate-100 dark:border-slate-700" />
|
||||
|
||||
{/* Connection type */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700 dark:text-slate-300">Режим подключения</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{([
|
||||
{ id: 'direct', label: 'Прямое подключение', icon: Globe, hint: 'Сервер делает запросы напрямую к NetUP' },
|
||||
{ id: 'agent', label: 'Через агент', icon: Wifi, hint: 'Запросы идут через Windows-агент в локальной сети' },
|
||||
] as const).map(opt => (
|
||||
<button
|
||||
key={opt.id}
|
||||
type="button"
|
||||
onClick={() => setConnectionType(opt.id)}
|
||||
className={cn(
|
||||
'flex items-start gap-3 p-3 rounded-xl border text-left transition-colors',
|
||||
connectionType === opt.id
|
||||
? 'border-violet-500 bg-violet-50 dark:bg-violet-900/20 text-violet-700 dark:text-violet-300'
|
||||
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:border-slate-300 dark:hover:border-slate-600',
|
||||
)}
|
||||
>
|
||||
<opt.icon size={16} className="shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">{opt.label}</p>
|
||||
<p className="text-xs opacity-70 mt-0.5">{opt.hint}</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent workstation selector */}
|
||||
{connectionType === 'agent' && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 dark:text-slate-300">Рабочее место с NetUP устройством</label>
|
||||
<select
|
||||
value={agentWorkstationId ?? ''}
|
||||
onChange={e => setAgentWorkstationId(e.target.value || null)}
|
||||
className="input w-full"
|
||||
>
|
||||
<option value="">— выберите рабочее место —</option>
|
||||
{workstations.map(ws => (
|
||||
<option key={ws.id} value={ws.id}>
|
||||
{ws.name}{ws.isOnline ? ' ✓ онлайн' : ' · офлайн'}{ws.agentVersion ? ` v${ws.agentVersion}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
На этом рабочем месте должно быть добавлено устройство типа «NetUp IPTV» в разделе Оборудование
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr className="border-slate-100 dark:border-slate-700" />
|
||||
|
||||
{/* Server URL */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-slate-700 dark:text-slate-300">Адрес сервера NetUP</label>
|
||||
<input
|
||||
type="text" value={serverUrl} onChange={e => setServerUrl(e.target.value)}
|
||||
placeholder="http://192.168.1.100:8880"
|
||||
placeholder={connectionType === 'agent' ? 'http://172.16.0.12:8880' : 'http://192.168.1.100:8880'}
|
||||
className="input w-full font-mono text-sm"
|
||||
/>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
Внешний адрес и порт, на который пробрасывается 172.16.0.12:80
|
||||
{connectionType === 'agent'
|
||||
? 'Локальный адрес NetUP в сети отеля (агент обращается к нему напрямую)'
|
||||
: 'Внешний адрес и порт, на который пробрасывается 172.16.0.12:80'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user