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>
This commit is contained in:
147
Frontend/src/components/RemoteDesktopButton.tsx
Normal file
147
Frontend/src/components/RemoteDesktopButton.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user