feat(api, web): implement short install link functionality for agents
- Added new API endpoints for creating, retrieving, and revoking install links for agents. - Enhanced the agent installation process with short links accessible via `/agent-install/:id` and `/:slug`. - Updated the README and documentation to reflect the new installation method and usage instructions. - Refactored relevant components in the web application to support the new install link feature. - Improved error handling and validation for install link operations. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- Agent install short-links (one-liner invites)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_install_links (
|
||||
id TEXT PRIMARY KEY,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
client_name TEXT NOT NULL,
|
||||
platform TEXT NOT NULL DEFAULT 'linux',
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
revoked_at TEXT,
|
||||
last_used_at TEXT,
|
||||
use_count INTEGER NOT NULL DEFAULT 0,
|
||||
CHECK (platform IN ('linux', 'mikrotik')),
|
||||
CHECK (length(trim(client_name)) > 0),
|
||||
CHECK (length(trim(id)) > 0),
|
||||
CHECK (length(trim(slug)) > 0)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_install_links_slug
|
||||
ON agent_install_links (slug);
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
policyRuleResolved,
|
||||
ipOverrides,
|
||||
agentStatsSamples,
|
||||
agentInstallLinks,
|
||||
settings,
|
||||
SHARED_POLICY_SET_ID,
|
||||
} from '../schema.js'
|
||||
@@ -434,6 +435,59 @@ export function cloneRulesFrom(
|
||||
return getAgent(db, targetAgentId)
|
||||
}
|
||||
|
||||
export function listInstallLinks(db: Db) {
|
||||
return db
|
||||
.select()
|
||||
.from(agentInstallLinks)
|
||||
.orderBy(desc(agentInstallLinks.createdAt))
|
||||
.all()
|
||||
}
|
||||
|
||||
export function getInstallLink(db: Db, id: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(agentInstallLinks)
|
||||
.where(eq(agentInstallLinks.id, id))
|
||||
.get()
|
||||
}
|
||||
|
||||
export function getInstallLinkBySlug(db: Db, slug: string) {
|
||||
return db
|
||||
.select()
|
||||
.from(agentInstallLinks)
|
||||
.where(eq(agentInstallLinks.slug, slug))
|
||||
.get()
|
||||
}
|
||||
|
||||
export function insertInstallLink(
|
||||
db: Db,
|
||||
row: typeof agentInstallLinks.$inferInsert,
|
||||
) {
|
||||
db.insert(agentInstallLinks).values(row).run()
|
||||
return getInstallLink(db, row.id)
|
||||
}
|
||||
|
||||
export function revokeInstallLink(db: Db, id: string) {
|
||||
const now = new Date().toISOString()
|
||||
db.update(agentInstallLinks)
|
||||
.set({ revokedAt: now })
|
||||
.where(eq(agentInstallLinks.id, id))
|
||||
.run()
|
||||
return getInstallLink(db, id)
|
||||
}
|
||||
|
||||
export function touchInstallLink(db: Db, id: string) {
|
||||
const now = new Date().toISOString()
|
||||
db.update(agentInstallLinks)
|
||||
.set({
|
||||
lastUsedAt: now,
|
||||
useCount: sql`${agentInstallLinks.useCount} + 1`,
|
||||
})
|
||||
.where(eq(agentInstallLinks.id, id))
|
||||
.run()
|
||||
return getInstallLink(db, id)
|
||||
}
|
||||
|
||||
export const repos = {
|
||||
listAgents,
|
||||
getAgent,
|
||||
@@ -481,6 +535,12 @@ export const repos = {
|
||||
setSetting,
|
||||
listSettings,
|
||||
cloneRulesFrom,
|
||||
listInstallLinks,
|
||||
getInstallLink,
|
||||
getInstallLinkBySlug,
|
||||
insertInstallLink,
|
||||
revokeInstallLink,
|
||||
touchInstallLink,
|
||||
}
|
||||
|
||||
export { SHARED_POLICY_SET_ID }
|
||||
|
||||
@@ -194,6 +194,26 @@ export const agentStatsSamples = sqliteTable(
|
||||
}),
|
||||
)
|
||||
|
||||
/** Short install invite links (`/agent-install/:id` and `/:slug`). */
|
||||
export const agentInstallLinks = sqliteTable(
|
||||
'agent_install_links',
|
||||
{
|
||||
id: text('id').primaryKey(),
|
||||
slug: text('slug').notNull(),
|
||||
clientName: text('client_name').notNull(),
|
||||
platform: text('platform').notNull().default('linux'), // linux | mikrotik
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
revokedAt: text('revoked_at'),
|
||||
lastUsedAt: text('last_used_at'),
|
||||
useCount: integer('use_count').notNull().default(0),
|
||||
},
|
||||
(t) => ({
|
||||
slugIdx: uniqueIndex('idx_agent_install_links_slug').on(t.slug),
|
||||
}),
|
||||
)
|
||||
|
||||
export const SHARED_POLICY_SET_ID = 'set-shared-default'
|
||||
|
||||
export const schema = {
|
||||
@@ -207,4 +227,5 @@ export const schema = {
|
||||
policyRuleResolved,
|
||||
ipOverrides,
|
||||
agentStatsSamples,
|
||||
agentInstallLinks,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user