Files
IT_Tool/Frontend/src/components/RemoteDesktopButton.tsx
Claude Agent 55e016c07d feat: Phase 7 — MeshCentral Remote Desktop Integration
Backend:
- MeshCentralOptions + MeshCentralService: Node-Lookup via Hostname, Remote-Desktop-URL-Generierung
- RemoteDesktopController: GET /api/v1/agents/{id}/remote-session mit 3 Status-Zuständen (nicht konfiguriert / Agent fehlt / bereit)
- Program.cs: HttpClient + MeshCentralService registriert, appsettings.json mit Konfigurationsblock

Go Agent:
- config.go: MeshCentralUrl + MeshEnabled Felder
- internal/meshagent/installer.go: MeshAgent Download + Installation (Windows Service / Linux systemd)
- main.go: Automatische MeshAgent-Installation nach Enrollment wenn aktiviert

Frontend:
- RemoteDesktopButton: Modales Dialog mit 3 Zustandsanzeigen (Setup nötig / Agent installieren / Remote Desktop öffnen)
- AgentDetailPage: RemoteDesktopButton im Header integriert
- api/types.ts + api/client.ts: RemoteSessionInfo Typ + remoteDesktopApi

docker-compose.yml: MeshCentral Service (ghcr.io/ylianst/meshcentral:latest, Ports 4430/4431)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:39:49 +01:00

148 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { Monitor, Loader2, AlertTriangle, ExternalLink } from 'lucide-react'
import { remoteDesktopApi } from '../api/client'
import type { RemoteSessionInfo } from '../api/types'
import { cn } from '../lib/utils'
interface RemoteDesktopButtonProps {
agentId: string
agentHostname: string
className?: string
}
export function RemoteDesktopButton({ agentId, agentHostname, className }: RemoteDesktopButtonProps) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [sessionInfo, setSessionInfo] = useState<RemoteSessionInfo | null>(null)
const [showModal, setShowModal] = useState(false)
async function handleClick() {
setLoading(true)
setError(null)
try {
const info = await remoteDesktopApi.getSession(agentId)
setSessionInfo(info)
setShowModal(true)
} catch (e) {
setError('Remote-Session konnte nicht geladen werden')
} finally {
setLoading(false)
}
}
function handleConnect() {
if (sessionInfo?.sessionUrl) {
window.open(sessionInfo.sessionUrl, `rmm-remote-${agentId}`,
'width=1280,height=800,scrollbars=no,toolbar=no,menubar=no')
}
}
return (
<>
<button
onClick={handleClick}
disabled={loading}
className={cn(
'flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors',
'bg-purple-500/20 text-purple-400 border border-purple-500/30 hover:bg-purple-500/30',
loading && 'opacity-50 cursor-not-allowed',
className,
)}
>
{loading ? <Loader2 size={16} className="animate-spin" /> : <Monitor size={16} />}
Remote Desktop
</button>
{/* Modal */}
{showModal && (
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50"
onClick={() => setShowModal(false)}>
<div className="bg-card border border-border rounded-xl p-6 w-full max-w-md shadow-2xl"
onClick={e => e.stopPropagation()}>
<div className="flex items-center gap-3 mb-4">
<div className="w-9 h-9 rounded-lg bg-purple-500/20 flex items-center justify-center">
<Monitor size={18} className="text-purple-400" />
</div>
<div>
<h3 className="font-semibold text-foreground">Remote Desktop</h3>
<p className="text-xs text-muted-foreground">{agentHostname}</p>
</div>
<button onClick={() => setShowModal(false)}
className="ml-auto text-muted-foreground hover:text-foreground text-lg leading-none">×</button>
</div>
{/* Status: nicht konfiguriert */}
{sessionInfo && !sessionInfo.configured && (
<div className="space-y-3">
<div className="flex items-start gap-2 p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<AlertTriangle size={16} className="text-yellow-400 mt-0.5 flex-shrink-0" />
<p className="text-sm text-yellow-300">{sessionInfo.message}</p>
</div>
<p className="text-sm text-muted-foreground">
MeshCentral läuft unter{' '}
<a href={sessionInfo.setupUrl} target="_blank" rel="noopener noreferrer"
className="text-primary hover:underline">{sessionInfo.setupUrl}</a>.
Richte MeshCentral ein und setze <code className="text-xs bg-muted px-1 rounded">MeshCentral:Enabled=true</code> in der appsettings.json.
</p>
<button onClick={() => window.open(sessionInfo.setupUrl, '_blank')}
className="flex items-center gap-2 w-full justify-center px-4 py-2 bg-yellow-500/20 text-yellow-400 border border-yellow-500/30 rounded-lg text-sm hover:bg-yellow-500/30">
<ExternalLink size={14} />
MeshCentral öffnen
</button>
</div>
)}
{/* Status: Agent nicht installiert */}
{sessionInfo?.configured && !sessionInfo.agentInstalled && (
<div className="space-y-3">
<div className="flex items-start gap-2 p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
<AlertTriangle size={16} className="text-orange-400 mt-0.5 flex-shrink-0" />
<p className="text-sm text-orange-300">{sessionInfo.message}</p>
</div>
<p className="text-sm text-muted-foreground">
Der NexusRMM-Agent installiert MeshAgent automatisch wenn{' '}
<code className="text-xs bg-muted px-1 rounded">mesh_enabled: true</code> in der Agent-Config gesetzt ist.
</p>
{sessionInfo.meshAgentDownloadUrl && (
<button onClick={() => window.open(sessionInfo.meshAgentDownloadUrl, '_blank')}
className="flex items-center gap-2 w-full justify-center px-4 py-2 bg-muted border border-border rounded-lg text-sm hover:bg-accent">
<ExternalLink size={14} />
MeshAgent manuell herunterladen
</button>
)}
</div>
)}
{/* Status: bereit */}
{sessionInfo?.configured && sessionInfo.agentInstalled && (
<div className="space-y-3">
<div className="p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
<p className="text-sm text-green-400 font-medium"> MeshAgent verbunden</p>
<p className="text-xs text-muted-foreground mt-1">Node ID: {sessionInfo.meshNodeId}</p>
</div>
<p className="text-sm text-muted-foreground">
Remote Desktop öffnet sich in einem neuen Fenster. Falls du nach einem Login gefragt wirst, melde dich bei MeshCentral an.
</p>
<button onClick={handleConnect}
className="flex items-center gap-2 w-full justify-center px-4 py-2 bg-purple-500/20 text-purple-400 border border-purple-500/30 rounded-lg text-sm hover:bg-purple-500/30">
<Monitor size={14} />
Remote Desktop öffnen
</button>
<button onClick={() => window.open(sessionInfo.meshCentralBaseUrl, '_blank')}
className="flex items-center gap-2 w-full justify-center px-4 py-2 bg-muted border border-border rounded-lg text-sm hover:bg-accent text-xs">
<ExternalLink size={12} />
MeshCentral Dashboard
</button>
</div>
)}
{error && (
<p className="text-sm text-red-400 mt-2">{error}</p>
)}
</div>
</div>
)}
</>
)
}