feat: melhorar gerenciamento de acesso remoto de máquinas
This commit is contained in:
parent
714b199879
commit
192a5c2909
5 changed files with 664 additions and 261 deletions
|
|
@ -1367,6 +1367,118 @@ export const toggleActive = mutation({
|
|||
},
|
||||
})
|
||||
|
||||
type RemoteAccessEntry = {
|
||||
id: string
|
||||
provider: string
|
||||
identifier: string
|
||||
url: string | null
|
||||
notes: string | null
|
||||
lastVerifiedAt: number | null
|
||||
metadata: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
function createRemoteAccessId() {
|
||||
return `ra_${Math.random().toString(36).slice(2, 8)}${Date.now().toString(36)}`
|
||||
}
|
||||
|
||||
function coerceString(value: unknown): string | null {
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function coerceNumber(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) return null
|
||||
const parsed = Number(trimmed)
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeRemoteAccessEntry(raw: unknown): RemoteAccessEntry | null {
|
||||
if (!raw) return null
|
||||
if (typeof raw === "string") {
|
||||
const trimmed = raw.trim()
|
||||
if (!trimmed) return null
|
||||
const isUrl = /^https?:\/\//i.test(trimmed)
|
||||
return {
|
||||
id: createRemoteAccessId(),
|
||||
provider: "Remoto",
|
||||
identifier: isUrl ? trimmed : trimmed,
|
||||
url: isUrl ? trimmed : null,
|
||||
notes: null,
|
||||
lastVerifiedAt: null,
|
||||
metadata: null,
|
||||
}
|
||||
}
|
||||
if (typeof raw !== "object") return null
|
||||
const record = raw as Record<string, unknown>
|
||||
const provider =
|
||||
coerceString(record.provider) ??
|
||||
coerceString(record.tool) ??
|
||||
coerceString(record.vendor) ??
|
||||
coerceString(record.name) ??
|
||||
"Remoto"
|
||||
const identifier =
|
||||
coerceString(record.identifier) ??
|
||||
coerceString(record.code) ??
|
||||
coerceString(record.id) ??
|
||||
coerceString(record.accessId)
|
||||
const url =
|
||||
coerceString(record.url) ??
|
||||
coerceString(record.link) ??
|
||||
coerceString(record.remoteUrl) ??
|
||||
coerceString(record.console) ??
|
||||
coerceString(record.viewer) ??
|
||||
null
|
||||
const resolvedIdentifier = identifier ?? url ?? "Acesso remoto"
|
||||
const notes = coerceString(record.notes) ?? coerceString(record.note) ?? coerceString(record.description) ?? coerceString(record.obs) ?? null
|
||||
const timestamp =
|
||||
coerceNumber(record.lastVerifiedAt) ??
|
||||
coerceNumber(record.verifiedAt) ??
|
||||
coerceNumber(record.checkedAt) ??
|
||||
coerceNumber(record.updatedAt) ??
|
||||
null
|
||||
const id = coerceString(record.id) ?? createRemoteAccessId()
|
||||
const metadata =
|
||||
record.metadata && typeof record.metadata === "object" && !Array.isArray(record.metadata)
|
||||
? (record.metadata as Record<string, unknown>)
|
||||
: null
|
||||
return {
|
||||
id,
|
||||
provider,
|
||||
identifier: resolvedIdentifier,
|
||||
url,
|
||||
notes,
|
||||
lastVerifiedAt: timestamp,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRemoteAccessList(raw: unknown): RemoteAccessEntry[] {
|
||||
const source = Array.isArray(raw) ? raw : raw ? [raw] : []
|
||||
const seen = new Set<string>()
|
||||
const entries: RemoteAccessEntry[] = []
|
||||
for (const item of source) {
|
||||
const entry = normalizeRemoteAccessEntry(item)
|
||||
if (!entry) continue
|
||||
let nextId = entry.id
|
||||
while (seen.has(nextId)) {
|
||||
nextId = createRemoteAccessId()
|
||||
}
|
||||
seen.add(nextId)
|
||||
entries.push(nextId === entry.id ? entry : { ...entry, id: nextId })
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
export const updateRemoteAccess = mutation({
|
||||
args: {
|
||||
machineId: v.id("machines"),
|
||||
|
|
@ -1375,9 +1487,11 @@ export const updateRemoteAccess = mutation({
|
|||
identifier: v.optional(v.string()),
|
||||
url: v.optional(v.string()),
|
||||
notes: v.optional(v.string()),
|
||||
action: v.optional(v.string()),
|
||||
entryId: v.optional(v.string()),
|
||||
clear: v.optional(v.boolean()),
|
||||
},
|
||||
handler: async (ctx, { machineId, actorId, provider, identifier, url, notes, clear }) => {
|
||||
handler: async (ctx, { machineId, actorId, provider, identifier, url, notes, action, entryId, clear }) => {
|
||||
const machine = await ctx.db.get(machineId)
|
||||
if (!machine) {
|
||||
throw new ConvexError("Máquina não encontrada")
|
||||
|
|
@ -1393,11 +1507,49 @@ export const updateRemoteAccess = mutation({
|
|||
throw new ConvexError("Somente administradores e agentes podem ajustar o acesso remoto.")
|
||||
}
|
||||
|
||||
if (clear) {
|
||||
const actionMode = (() => {
|
||||
if (clear) return "clear" as const
|
||||
const normalized = (action ?? "").toLowerCase()
|
||||
if (normalized === "clear") return "clear" as const
|
||||
if (normalized === "delete" || normalized === "remove") return "delete" as const
|
||||
return "upsert" as const
|
||||
})()
|
||||
|
||||
const existingEntries = normalizeRemoteAccessList(machine.remoteAccess)
|
||||
|
||||
if (actionMode === "clear") {
|
||||
await ctx.db.patch(machineId, { remoteAccess: null, updatedAt: Date.now() })
|
||||
return { remoteAccess: null }
|
||||
}
|
||||
|
||||
if (actionMode === "delete") {
|
||||
const trimmedEntryId = coerceString(entryId)
|
||||
const trimmedProvider = coerceString(provider)
|
||||
const trimmedIdentifier = coerceString(identifier)
|
||||
let target: RemoteAccessEntry | undefined
|
||||
if (trimmedEntryId) {
|
||||
target = existingEntries.find((entry) => entry.id === trimmedEntryId)
|
||||
}
|
||||
if (!target && trimmedProvider && trimmedIdentifier) {
|
||||
target = existingEntries.find(
|
||||
(entry) => entry.provider === trimmedProvider && entry.identifier === trimmedIdentifier
|
||||
)
|
||||
}
|
||||
if (!target && trimmedIdentifier) {
|
||||
target = existingEntries.find((entry) => entry.identifier === trimmedIdentifier)
|
||||
}
|
||||
if (!target && trimmedProvider) {
|
||||
target = existingEntries.find((entry) => entry.provider === trimmedProvider)
|
||||
}
|
||||
if (!target) {
|
||||
throw new ConvexError("Entrada de acesso remoto não encontrada.")
|
||||
}
|
||||
const nextEntries = existingEntries.filter((entry) => entry.id !== target!.id)
|
||||
const nextValue = nextEntries.length > 0 ? nextEntries : null
|
||||
await ctx.db.patch(machineId, { remoteAccess: nextValue, updatedAt: Date.now() })
|
||||
return { remoteAccess: nextValue }
|
||||
}
|
||||
|
||||
const trimmedProvider = (provider ?? "").trim()
|
||||
const trimmedIdentifier = (identifier ?? "").trim()
|
||||
if (!trimmedProvider || !trimmedIdentifier) {
|
||||
|
|
@ -1422,8 +1574,15 @@ export const updateRemoteAccess = mutation({
|
|||
|
||||
const cleanedNotes = notes?.trim() ? notes.trim() : null
|
||||
const lastVerifiedAt = Date.now()
|
||||
const targetEntryId =
|
||||
coerceString(entryId) ??
|
||||
existingEntries.find(
|
||||
(entry) => entry.provider === trimmedProvider && entry.identifier === trimmedIdentifier
|
||||
)?.id ??
|
||||
createRemoteAccessId()
|
||||
|
||||
const remoteAccess = {
|
||||
const updatedEntry: RemoteAccessEntry = {
|
||||
id: targetEntryId,
|
||||
provider: trimmedProvider,
|
||||
identifier: trimmedIdentifier,
|
||||
url: normalizedUrl,
|
||||
|
|
@ -1438,12 +1597,21 @@ export const updateRemoteAccess = mutation({
|
|||
},
|
||||
}
|
||||
|
||||
const existingIndex = existingEntries.findIndex((entry) => entry.id === targetEntryId)
|
||||
let nextEntries: RemoteAccessEntry[]
|
||||
if (existingIndex >= 0) {
|
||||
nextEntries = [...existingEntries]
|
||||
nextEntries[existingIndex] = updatedEntry
|
||||
} else {
|
||||
nextEntries = [...existingEntries, updatedEntry]
|
||||
}
|
||||
|
||||
await ctx.db.patch(machineId, {
|
||||
remoteAccess,
|
||||
remoteAccess: nextEntries,
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
|
||||
return { remoteAccess }
|
||||
return { remoteAccess: nextEntries }
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue