feat: TTLock — scan TTHotel sources for API config (CE_ConfigServer)
- Agent: ttlock:find_api_config command scans TTHotel app JS files for CE_ConfigServer calls, oauth2 endpoints, API server URLs - Backend: POST /ttlock/find-api-config route - UI: Search button on each workstation row to trigger scan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -131,6 +131,35 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ── POST /api/hotels/:slug/ttlock/find-api-config ───────────────────────────
|
||||||
|
// Ищет в исходниках TTHotel вызовы CE_ConfigServer и API-сервер
|
||||||
|
fastify.post<SlugParam & { Body: { workstation_id: string } }>(
|
||||||
|
'/api/hotels/:slug/ttlock/find-api-config',
|
||||||
|
{ onRequest: [fastify.authenticate] },
|
||||||
|
async (req, reply) => {
|
||||||
|
const { slug } = req.params
|
||||||
|
if (!canManage(req.user.hotelSlug, req.user.role, slug))
|
||||||
|
return reply.code(403).send({ error: 'Forbidden' })
|
||||||
|
const hotelId = await getHotelId(slug)
|
||||||
|
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||||
|
|
||||||
|
const { workstation_id } = req.body
|
||||||
|
if (!workstation_id) return reply.code(400).send({ error: 'workstation_id required' })
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await sendCommandAndWait(workstation_id, {
|
||||||
|
type: 'ttlock:find_api_config',
|
||||||
|
}, 20_000) as { ok?: boolean; appDir?: string; results?: { path: string; matches: string[] }[]; error?: string }
|
||||||
|
|
||||||
|
if (!result.ok) return reply.code(502).send({ error: result.error ?? 'Ничего не найдено', appDir: result.appDir })
|
||||||
|
return { ok: true, appDir: result.appDir, results: result.results }
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : 'Агент не ответил'
|
||||||
|
return reply.code(502).send({ error: msg })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// ── POST /api/hotels/:slug/ttlock/test-api ──────────────────────────────────
|
// ── POST /api/hotels/:slug/ttlock/test-api ──────────────────────────────────
|
||||||
// Проверяет подключение к TTLock Cloud API (OAuth + список замков)
|
// Проверяет подключение к TTLock Cloud API (OAuth + список замков)
|
||||||
fastify.post<SlugParam & { Body: { workstation_id: string } }>(
|
fastify.post<SlugParam & { Body: { workstation_id: string } }>(
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ export const api = {
|
|||||||
testApi: (slug: string, workstationId: string) =>
|
testApi: (slug: string, workstationId: string) =>
|
||||||
req<{ ok: boolean; lockCount?: number }>('POST', `/api/hotels/${slug}/ttlock/test-api`, { workstation_id: workstationId }),
|
req<{ ok: boolean; lockCount?: number }>('POST', `/api/hotels/${slug}/ttlock/test-api`, { workstation_id: workstationId }),
|
||||||
|
|
||||||
|
findApiConfig: (slug: string, workstationId: string) =>
|
||||||
|
req<{ ok: boolean; appDir?: string; results?: { path: string; matches: string[] }[] }>('POST', `/api/hotels/${slug}/ttlock/find-api-config`, { workstation_id: workstationId }),
|
||||||
|
|
||||||
dllInfo: (slug: string, workstationId: string) =>
|
dllInfo: (slug: string, workstationId: string) =>
|
||||||
req<{ ok: boolean; content: string; path: string }>('GET', `/api/hotels/${slug}/ttlock/dll-info?workstation_id=${workstationId}`),
|
req<{ ok: boolean; content: string; path: string }>('GET', `/api/hotels/${slug}/ttlock/dll-info?workstation_id=${workstationId}`),
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as XLSX from 'xlsx'
|
|||||||
import {
|
import {
|
||||||
KeyRound, Save, RefreshCw, Check, X, AlertCircle,
|
KeyRound, Save, RefreshCw, Check, X, AlertCircle,
|
||||||
ChevronDown, ChevronRight, Trash2, Plus, Eye, EyeOff,
|
ChevronDown, ChevronRight, Trash2, Plus, Eye, EyeOff,
|
||||||
Lock, Unlock, Info, HardDriveDownload, Zap, Terminal, Upload, Globe,
|
Lock, Unlock, Info, HardDriveDownload, Zap, Terminal, Upload, Globe, Search,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { api, type TTLockConfig, type RoomLockMapping, type Workstation } from '../lib/api'
|
import { api, type TTLockConfig, type RoomLockMapping, type Workstation } from '../lib/api'
|
||||||
import { useAuth } from '../contexts/AuthContext'
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
@@ -633,6 +633,35 @@ export function TTLockPage() {
|
|||||||
>
|
>
|
||||||
<Terminal size={13} />
|
<Terminal size={13} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Find TTHotel API config */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!ws.isOnline}
|
||||||
|
onClick={async () => {
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
const result = await api.ttlock.findApiConfig(slug, ws.id)
|
||||||
|
if (!result.results?.length) {
|
||||||
|
showSuccess('Ничего не найдено в исходниках TTHotel')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const out = result.results.map(r =>
|
||||||
|
`📄 ${r.path}\n${r.matches.join('\n')}`
|
||||||
|
).join('\n\n')
|
||||||
|
// Выводим в консоль браузера и в alert для простоты
|
||||||
|
console.log('[TTHotel API config scan]\n', out)
|
||||||
|
alert(`Найдено в TTHotel (см. консоль браузера):\n\n${out.slice(0, 2000)}`)
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? e.message : 'Ошибка сканирования'
|
||||||
|
setError(msg)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="btn-secondary p-1.5 shrink-0"
|
||||||
|
title={!ws.isOnline ? 'Агент офлайн' : 'Найти API-сервер TTHotel в исходниках'}
|
||||||
|
>
|
||||||
|
<Search size={13} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user