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:
@@ -29,10 +29,15 @@ import {
|
||||
import {
|
||||
mapInstallLink,
|
||||
randomToken,
|
||||
buildInstallUrls,
|
||||
} from '../services/install-links.js'
|
||||
import { hashToken } from '../plugins/auth.js'
|
||||
import type { AppConfig } from '../config.js'
|
||||
|
||||
function mapAgent(a: NonNullable<ReturnType<typeof repos.getAgent>>) {
|
||||
function mapAgent(
|
||||
a: NonNullable<ReturnType<typeof repos.getAgent>>,
|
||||
opts?: { installCurl?: string | null; installLinkId?: string | null },
|
||||
) {
|
||||
return {
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
@@ -55,6 +60,8 @@ function mapAgent(a: NonNullable<ReturnType<typeof repos.getAgent>>) {
|
||||
created_at: a.createdAt,
|
||||
approved_at: a.approvedAt,
|
||||
revoked_at: a.revokedAt,
|
||||
install_curl: opts?.installCurl ?? null,
|
||||
install_link_id: opts?.installLinkId ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,20 +155,49 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
|
||||
app.post('/install-links', async (req, reply) => {
|
||||
const body = createInstallLinkBodySchema.parse(req.body)
|
||||
const id = randomToken(10)
|
||||
const linkId = randomToken(10)
|
||||
const slug = randomToken(16)
|
||||
if (repos.getInstallLink(app.db, id) || repos.getInstallLinkBySlug(app.db, slug)) {
|
||||
if (
|
||||
repos.getInstallLink(app.db, linkId) ||
|
||||
repos.getInstallLinkBySlug(app.db, slug)
|
||||
) {
|
||||
throw new AppError('CONFLICT', 'Retry create (id collision)', 409)
|
||||
}
|
||||
const row = repos.insertInstallLink(app.db, {
|
||||
id,
|
||||
slug,
|
||||
clientName: body.name.trim(),
|
||||
platform: body.platform ?? 'linux',
|
||||
createdAt: new Date().toISOString(),
|
||||
useCount: 0,
|
||||
})
|
||||
return reply.code(201).send(mapInstallLink(row!, config.publicBaseUrl))
|
||||
|
||||
const agentId = crypto.randomUUID()
|
||||
const inviteToken = `invite:${agentId}`
|
||||
const now = new Date().toISOString()
|
||||
const name = body.name.trim()
|
||||
const platform = body.platform ?? 'linux'
|
||||
|
||||
app.sqlite.transaction(() => {
|
||||
repos.insertAgent(app.db, {
|
||||
id: agentId,
|
||||
name,
|
||||
hostname: null,
|
||||
platform,
|
||||
tokenPrefix: inviteToken.slice(0, 12),
|
||||
tokenHash: hashToken(inviteToken),
|
||||
status: 'invited',
|
||||
policyMode: 'blacklist',
|
||||
policyGeneration: 1,
|
||||
clientVersion: null,
|
||||
settingsJson: '{}',
|
||||
createdAt: now,
|
||||
})
|
||||
repos.insertInstallLink(app.db, {
|
||||
id: linkId,
|
||||
slug,
|
||||
clientName: name,
|
||||
platform,
|
||||
agentId,
|
||||
createdAt: now,
|
||||
useCount: 0,
|
||||
})
|
||||
})()
|
||||
|
||||
const row = repos.getInstallLink(app.db, linkId)!
|
||||
return reply.code(201).send(mapInstallLink(row, config.publicBaseUrl))
|
||||
})
|
||||
|
||||
app.delete<{ Params: { id: string } }>(
|
||||
@@ -175,9 +211,27 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
|
||||
)
|
||||
|
||||
// Agents
|
||||
app.get('/agents', async () => ({
|
||||
items: repos.listAgents(app.db).map(mapAgent),
|
||||
}))
|
||||
app.get('/agents', async () => {
|
||||
const all = repos.listAgents(app.db)
|
||||
return {
|
||||
items: all.map((a) => {
|
||||
const link = repos.getInstallLinkByAgentId(app.db, a.id)
|
||||
if (!link || link.revokedAt) {
|
||||
return mapAgent(a)
|
||||
}
|
||||
const urls = buildInstallUrls(
|
||||
config.publicBaseUrl,
|
||||
link.id,
|
||||
link.slug,
|
||||
link.platform === 'mikrotik' ? 'mikrotik' : 'linux',
|
||||
)
|
||||
return mapAgent(a, {
|
||||
installCurl: urls.curl.by_slug,
|
||||
installLinkId: link.id,
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
app.get<{ Params: { id: string } }>('/agents/:id', async (req) => {
|
||||
const a = repos.getAgent(app.db, req.params.id)
|
||||
|
||||
Reference in New Issue
Block a user