feat(api, web): enhance agent installation process with invited status and policy support
- Updated the agent enrollment process to include an 'invited' status, allowing for better tracking of agent states. - Implemented support for install links that can now include an `install_link_id`, facilitating the transition from invited to pending status upon enrollment. - Enhanced the MikroTik installation script to include the `EvofwInstallLinkId` for better tracking and management. - Added new API endpoints for fetching agent policies and serving MikroTik-specific installation scripts. - Improved the web UI to reflect the new agent statuses and provide copyable installation commands for agents. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@evofw/ui/components/sheet'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
|
||||
/**
|
||||
* Create agent install invite — Sheet.
|
||||
@@ -44,12 +45,9 @@ interface AddAgentSheetProps {
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
async function copyText(text: string) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
toast.success('Скопировано')
|
||||
}
|
||||
|
||||
export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
const qc = useQueryClient()
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
const [name, setName] = useState('web-01')
|
||||
const [platform, setPlatform] = useState<Platform>('linux')
|
||||
const [created, setCreated] = useState<InstallLink | null>(null)
|
||||
@@ -70,13 +68,20 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
}),
|
||||
onSuccess: (link) => {
|
||||
setCreated(link)
|
||||
toast.success('Ссылка создана')
|
||||
toast.success('Агент создан')
|
||||
void qc.invalidateQueries({ queryKey: ['agents'] })
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
})
|
||||
|
||||
const canCreate = Boolean(name.trim()) && !create.isPending
|
||||
|
||||
function handleCopy(text: string) {
|
||||
if (!text) return
|
||||
copyToClipboard(text)
|
||||
toast.success('Скопировано')
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent className="flex flex-col gap-0 overflow-hidden sm:max-w-md">
|
||||
@@ -86,8 +91,8 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
</SheetTitle>
|
||||
<SheetDescription>
|
||||
{created
|
||||
? 'Скопируйте one-liner и выполните на хосте. Затем одобрите агента в списке.'
|
||||
: 'Создайте короткую install-ссылку с именем клиента.'}
|
||||
? 'Агент уже в списке (Invited). Скопируйте one-liner и выполните на хосте.'
|
||||
: 'Создайте агента и короткую install-ссылку.'}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
@@ -139,9 +144,7 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="self-start"
|
||||
onClick={() =>
|
||||
void copyText(created.curl?.by_id ?? '')
|
||||
}
|
||||
onClick={() => handleCopy(created.curl?.by_id ?? '')}
|
||||
>
|
||||
<Copy data-icon="inline-start" />
|
||||
Копировать
|
||||
@@ -159,9 +162,7 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="self-start"
|
||||
onClick={() =>
|
||||
void copyText(created.curl?.by_slug ?? '')
|
||||
}
|
||||
onClick={() => handleCopy(created.curl?.by_slug ?? '')}
|
||||
>
|
||||
<Copy data-icon="inline-start" />
|
||||
Копировать
|
||||
@@ -182,7 +183,7 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
setCreated(null)
|
||||
}}
|
||||
>
|
||||
Ещё ссылка
|
||||
Ещё агент
|
||||
</Button>
|
||||
<Button onClick={() => onOpenChange(false)}>Готово</Button>
|
||||
</>
|
||||
@@ -191,11 +192,8 @@ export function AddAgentSheet({ open, onOpenChange }: AddAgentSheetProps) {
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!canCreate}
|
||||
onClick={() => create.mutate()}
|
||||
>
|
||||
Создать ссылку
|
||||
<Button disabled={!canCreate} onClick={() => create.mutate()}>
|
||||
Создать
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -16,6 +16,7 @@ const STATUS_VARIANT: Record<string, BadgeVariant> = {
|
||||
allow: 'success-light',
|
||||
pending_push: 'secondary',
|
||||
pending: 'warning-light',
|
||||
invited: 'info-light',
|
||||
warning: 'warning-light',
|
||||
degraded: 'warning-light',
|
||||
conflict: 'destructive-light',
|
||||
@@ -54,6 +55,7 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
synced: 'Синхронизировано',
|
||||
pending_push: 'Ожидает отправки',
|
||||
pending: 'Pending',
|
||||
invited: 'Invited',
|
||||
approved: 'Approved',
|
||||
revoked: 'Revoked',
|
||||
enabled: 'Включён',
|
||||
|
||||
Reference in New Issue
Block a user