feat: integrar credenciais rustdesk aos acessos remotos
This commit is contained in:
parent
4079f67fcb
commit
07d631de40
5 changed files with 243 additions and 5 deletions
|
|
@ -1991,6 +1991,8 @@ type RemoteAccessEntry = {
|
||||||
provider: string
|
provider: string
|
||||||
identifier: string
|
identifier: string
|
||||||
url: string | null
|
url: string | null
|
||||||
|
username: string | null
|
||||||
|
password: string | null
|
||||||
notes: string | null
|
notes: string | null
|
||||||
lastVerifiedAt: number | null
|
lastVerifiedAt: number | null
|
||||||
metadata: Record<string, unknown> | null
|
metadata: Record<string, unknown> | null
|
||||||
|
|
@ -2032,6 +2034,8 @@ function normalizeRemoteAccessEntry(raw: unknown): RemoteAccessEntry | null {
|
||||||
provider: "Remoto",
|
provider: "Remoto",
|
||||||
identifier: isUrl ? trimmed : trimmed,
|
identifier: isUrl ? trimmed : trimmed,
|
||||||
url: isUrl ? trimmed : null,
|
url: isUrl ? trimmed : null,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
notes: null,
|
notes: null,
|
||||||
lastVerifiedAt: null,
|
lastVerifiedAt: null,
|
||||||
metadata: null,
|
metadata: null,
|
||||||
|
|
@ -2059,6 +2063,19 @@ function normalizeRemoteAccessEntry(raw: unknown): RemoteAccessEntry | null {
|
||||||
null
|
null
|
||||||
const resolvedIdentifier = identifier ?? url ?? "Acesso remoto"
|
const resolvedIdentifier = identifier ?? url ?? "Acesso remoto"
|
||||||
const notes = coerceString(record.notes) ?? coerceString(record.note) ?? coerceString(record.description) ?? coerceString(record.obs) ?? null
|
const notes = coerceString(record.notes) ?? coerceString(record.note) ?? coerceString(record.description) ?? coerceString(record.obs) ?? null
|
||||||
|
const username =
|
||||||
|
coerceString((record as Record<string, unknown>).username) ??
|
||||||
|
coerceString((record as Record<string, unknown>).user) ??
|
||||||
|
coerceString((record as Record<string, unknown>).login) ??
|
||||||
|
coerceString((record as Record<string, unknown>).email) ??
|
||||||
|
coerceString((record as Record<string, unknown>).account) ??
|
||||||
|
null
|
||||||
|
const password =
|
||||||
|
coerceString((record as Record<string, unknown>).password) ??
|
||||||
|
coerceString((record as Record<string, unknown>).pass) ??
|
||||||
|
coerceString((record as Record<string, unknown>).secret) ??
|
||||||
|
coerceString((record as Record<string, unknown>).pin) ??
|
||||||
|
null
|
||||||
const timestamp =
|
const timestamp =
|
||||||
coerceNumber(record.lastVerifiedAt) ??
|
coerceNumber(record.lastVerifiedAt) ??
|
||||||
coerceNumber(record.verifiedAt) ??
|
coerceNumber(record.verifiedAt) ??
|
||||||
|
|
@ -2075,6 +2092,8 @@ function normalizeRemoteAccessEntry(raw: unknown): RemoteAccessEntry | null {
|
||||||
provider,
|
provider,
|
||||||
identifier: resolvedIdentifier,
|
identifier: resolvedIdentifier,
|
||||||
url,
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
notes,
|
notes,
|
||||||
lastVerifiedAt: timestamp,
|
lastVerifiedAt: timestamp,
|
||||||
metadata,
|
metadata,
|
||||||
|
|
@ -2105,12 +2124,14 @@ export const updateRemoteAccess = mutation({
|
||||||
provider: v.optional(v.string()),
|
provider: v.optional(v.string()),
|
||||||
identifier: v.optional(v.string()),
|
identifier: v.optional(v.string()),
|
||||||
url: v.optional(v.string()),
|
url: v.optional(v.string()),
|
||||||
|
username: v.optional(v.string()),
|
||||||
|
password: v.optional(v.string()),
|
||||||
notes: v.optional(v.string()),
|
notes: v.optional(v.string()),
|
||||||
action: v.optional(v.string()),
|
action: v.optional(v.string()),
|
||||||
entryId: v.optional(v.string()),
|
entryId: v.optional(v.string()),
|
||||||
clear: v.optional(v.boolean()),
|
clear: v.optional(v.boolean()),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { machineId, actorId, provider, identifier, url, notes, action, entryId, clear }) => {
|
handler: async (ctx, { machineId, actorId, provider, identifier, url, username, password, notes, action, entryId, clear }) => {
|
||||||
const machine = await ctx.db.get(machineId)
|
const machine = await ctx.db.get(machineId)
|
||||||
if (!machine) {
|
if (!machine) {
|
||||||
throw new ConvexError("Dispositivo não encontrada")
|
throw new ConvexError("Dispositivo não encontrada")
|
||||||
|
|
@ -2192,6 +2213,8 @@ export const updateRemoteAccess = mutation({
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanedNotes = notes?.trim() ? notes.trim() : null
|
const cleanedNotes = notes?.trim() ? notes.trim() : null
|
||||||
|
const cleanedUsername = username?.trim() ? username.trim() : null
|
||||||
|
const cleanedPassword = password?.trim() ? password.trim() : null
|
||||||
const lastVerifiedAt = Date.now()
|
const lastVerifiedAt = Date.now()
|
||||||
const targetEntryId =
|
const targetEntryId =
|
||||||
coerceString(entryId) ??
|
coerceString(entryId) ??
|
||||||
|
|
@ -2205,12 +2228,16 @@ export const updateRemoteAccess = mutation({
|
||||||
provider: trimmedProvider,
|
provider: trimmedProvider,
|
||||||
identifier: trimmedIdentifier,
|
identifier: trimmedIdentifier,
|
||||||
url: normalizedUrl,
|
url: normalizedUrl,
|
||||||
|
username: cleanedUsername,
|
||||||
|
password: cleanedPassword,
|
||||||
notes: cleanedNotes,
|
notes: cleanedNotes,
|
||||||
lastVerifiedAt,
|
lastVerifiedAt,
|
||||||
metadata: {
|
metadata: {
|
||||||
provider: trimmedProvider,
|
provider: trimmedProvider,
|
||||||
identifier: trimmedIdentifier,
|
identifier: trimmedIdentifier,
|
||||||
url: normalizedUrl,
|
url: normalizedUrl,
|
||||||
|
username: cleanedUsername,
|
||||||
|
password: cleanedPassword,
|
||||||
notes: cleanedNotes,
|
notes: cleanedNotes,
|
||||||
lastVerifiedAt,
|
lastVerifiedAt,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ const schema = z.object({
|
||||||
provider: z.string().optional(),
|
provider: z.string().optional(),
|
||||||
identifier: z.string().optional(),
|
identifier: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
password: z.string().optional(),
|
||||||
notes: z.string().optional(),
|
notes: z.string().optional(),
|
||||||
entryId: z.string().optional(),
|
entryId: z.string().optional(),
|
||||||
action: z.enum(["save", "upsert", "clear", "delete", "remove"]).optional(),
|
action: z.enum(["save", "upsert", "clear", "delete", "remove"]).optional(),
|
||||||
|
|
@ -106,6 +108,8 @@ export async function POST(request: Request) {
|
||||||
if (provider) mutationArgs.provider = provider
|
if (provider) mutationArgs.provider = provider
|
||||||
if (identifier) mutationArgs.identifier = identifier
|
if (identifier) mutationArgs.identifier = identifier
|
||||||
if (normalizedUrl !== undefined) mutationArgs.url = normalizedUrl
|
if (normalizedUrl !== undefined) mutationArgs.url = normalizedUrl
|
||||||
|
mutationArgs.username = (parsed.username ?? "").trim()
|
||||||
|
mutationArgs.password = (parsed.password ?? "").trim()
|
||||||
if (notes.length) mutationArgs.notes = notes
|
if (notes.length) mutationArgs.notes = notes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ import {
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Key,
|
Key,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
MonitorSmartphone,
|
||||||
Globe,
|
Globe,
|
||||||
Apple,
|
Apple,
|
||||||
Terminal,
|
Terminal,
|
||||||
|
|
@ -323,6 +326,8 @@ type DeviceRemoteAccessEntry = {
|
||||||
clientId: string
|
clientId: string
|
||||||
provider: string | null
|
provider: string | null
|
||||||
identifier: string | null
|
identifier: string | null
|
||||||
|
username: string | null
|
||||||
|
password: string | null
|
||||||
url: string | null
|
url: string | null
|
||||||
notes: string | null
|
notes: string | null
|
||||||
lastVerifiedAt: number | null
|
lastVerifiedAt: number | null
|
||||||
|
|
@ -332,6 +337,8 @@ type DeviceRemoteAccessEntry = {
|
||||||
export type DeviceRemoteAccess = {
|
export type DeviceRemoteAccess = {
|
||||||
provider: string | null
|
provider: string | null
|
||||||
identifier: string | null
|
identifier: string | null
|
||||||
|
username: string | null
|
||||||
|
password: string | null
|
||||||
url: string | null
|
url: string | null
|
||||||
notes: string | null
|
notes: string | null
|
||||||
lastVerifiedAt: number | null
|
lastVerifiedAt: number | null
|
||||||
|
|
@ -411,6 +418,8 @@ function normalizeDeviceRemoteAccessEntry(raw: unknown): DeviceRemoteAccessEntry
|
||||||
const identifier =
|
const identifier =
|
||||||
readString(record, "identifier", "code", "id", "accessId") ??
|
readString(record, "identifier", "code", "id", "accessId") ??
|
||||||
readString(record, "value", "label")
|
readString(record, "value", "label")
|
||||||
|
const username = readString(record, "username", "user", "login", "email", "account") ?? null
|
||||||
|
const password = readString(record, "password", "pass", "secret", "pin") ?? null
|
||||||
const url = readString(record, "url", "link", "remoteUrl", "console", "viewer") ?? null
|
const url = readString(record, "url", "link", "remoteUrl", "console", "viewer") ?? null
|
||||||
const notes = readString(record, "notes", "note", "description", "obs") ?? null
|
const notes = readString(record, "notes", "note", "description", "obs") ?? null
|
||||||
const timestampCandidate =
|
const timestampCandidate =
|
||||||
|
|
@ -423,6 +432,8 @@ function normalizeDeviceRemoteAccessEntry(raw: unknown): DeviceRemoteAccessEntry
|
||||||
clientId: id ?? createRemoteAccessClientId(),
|
clientId: id ?? createRemoteAccessClientId(),
|
||||||
provider,
|
provider,
|
||||||
identifier: identifier ?? url ?? null,
|
identifier: identifier ?? url ?? null,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
url,
|
url,
|
||||||
notes,
|
notes,
|
||||||
lastVerifiedAt,
|
lastVerifiedAt,
|
||||||
|
|
@ -433,8 +444,8 @@ function normalizeDeviceRemoteAccessEntry(raw: unknown): DeviceRemoteAccessEntry
|
||||||
export function normalizeDeviceRemoteAccess(raw: unknown): DeviceRemoteAccess | null {
|
export function normalizeDeviceRemoteAccess(raw: unknown): DeviceRemoteAccess | null {
|
||||||
const entry = normalizeDeviceRemoteAccessEntry(raw)
|
const entry = normalizeDeviceRemoteAccessEntry(raw)
|
||||||
if (!entry) return null
|
if (!entry) return null
|
||||||
const { provider, identifier, url, notes, lastVerifiedAt, metadata } = entry
|
const { provider, identifier, username, password, url, notes, lastVerifiedAt, metadata } = entry
|
||||||
return { provider, identifier, url, notes, lastVerifiedAt, metadata }
|
return { provider, identifier, username, password, url, notes, lastVerifiedAt, metadata }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeDeviceRemoteAccessList(raw: unknown): DeviceRemoteAccessEntry[] {
|
export function normalizeDeviceRemoteAccessList(raw: unknown): DeviceRemoteAccessEntry[] {
|
||||||
|
|
@ -464,6 +475,15 @@ const REMOTE_ACCESS_METADATA_IGNORED_KEYS = new Set([
|
||||||
"code",
|
"code",
|
||||||
"id",
|
"id",
|
||||||
"accessId",
|
"accessId",
|
||||||
|
"username",
|
||||||
|
"user",
|
||||||
|
"login",
|
||||||
|
"email",
|
||||||
|
"account",
|
||||||
|
"password",
|
||||||
|
"pass",
|
||||||
|
"secret",
|
||||||
|
"pin",
|
||||||
"url",
|
"url",
|
||||||
"link",
|
"link",
|
||||||
"remoteUrl",
|
"remoteUrl",
|
||||||
|
|
@ -515,6 +535,25 @@ function readText(record: Record<string, unknown>, ...keys: string[]): string |
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRustDeskAccess(entry: DeviceRemoteAccessEntry | null | undefined) {
|
||||||
|
if (!entry) return false
|
||||||
|
const provider = (entry.provider ?? entry.metadata?.provider ?? "").toString().toLowerCase()
|
||||||
|
if (provider.includes("rustdesk")) return true
|
||||||
|
const url = (entry.url ?? entry.metadata?.url ?? "").toString().toLowerCase()
|
||||||
|
return url.includes("rustdesk")
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRustDeskUri(entry: DeviceRemoteAccessEntry) {
|
||||||
|
const identifier = (entry.identifier ?? "").replace(/\s+/g, "")
|
||||||
|
if (!identifier) return null
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
if (entry.password) {
|
||||||
|
params.set("password", entry.password)
|
||||||
|
}
|
||||||
|
const query = params.toString()
|
||||||
|
return `rustdesk://${encodeURIComponent(identifier)}${query ? `?${query}` : ""}`
|
||||||
|
}
|
||||||
|
|
||||||
function parseWindowsInstallDate(value: unknown): Date | null {
|
function parseWindowsInstallDate(value: unknown): Date | null {
|
||||||
if (!value) return null
|
if (!value) return null
|
||||||
if (value instanceof Date) return value
|
if (value instanceof Date) return value
|
||||||
|
|
@ -2999,9 +3038,12 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
)
|
)
|
||||||
const [remoteAccessCustomProvider, setRemoteAccessCustomProvider] = useState("")
|
const [remoteAccessCustomProvider, setRemoteAccessCustomProvider] = useState("")
|
||||||
const [remoteAccessIdentifierInput, setRemoteAccessIdentifierInput] = useState("")
|
const [remoteAccessIdentifierInput, setRemoteAccessIdentifierInput] = useState("")
|
||||||
|
const [remoteAccessUsernameInput, setRemoteAccessUsernameInput] = useState("")
|
||||||
|
const [remoteAccessPasswordInput, setRemoteAccessPasswordInput] = useState("")
|
||||||
const [remoteAccessUrlInput, setRemoteAccessUrlInput] = useState("")
|
const [remoteAccessUrlInput, setRemoteAccessUrlInput] = useState("")
|
||||||
const [remoteAccessNotesInput, setRemoteAccessNotesInput] = useState("")
|
const [remoteAccessNotesInput, setRemoteAccessNotesInput] = useState("")
|
||||||
const [remoteAccessSaving, setRemoteAccessSaving] = useState(false)
|
const [remoteAccessSaving, setRemoteAccessSaving] = useState(false)
|
||||||
|
const [visibleRemoteSecrets, setVisibleRemoteSecrets] = useState<Record<string, boolean>>({})
|
||||||
const editingRemoteAccess = useMemo(
|
const editingRemoteAccess = useMemo(
|
||||||
() => remoteAccessEntries.find((entry) => entry.clientId === editingRemoteAccessClientId) ?? null,
|
() => remoteAccessEntries.find((entry) => entry.clientId === editingRemoteAccessClientId) ?? null,
|
||||||
[editingRemoteAccessClientId, remoteAccessEntries]
|
[editingRemoteAccessClientId, remoteAccessEntries]
|
||||||
|
|
@ -3070,6 +3112,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
setRemoteAccessCustomProvider(providerName ?? "")
|
setRemoteAccessCustomProvider(providerName ?? "")
|
||||||
}
|
}
|
||||||
setRemoteAccessIdentifierInput(editingRemoteAccess?.identifier ?? "")
|
setRemoteAccessIdentifierInput(editingRemoteAccess?.identifier ?? "")
|
||||||
|
setRemoteAccessUsernameInput(editingRemoteAccess?.username ?? "")
|
||||||
|
setRemoteAccessPasswordInput(editingRemoteAccess?.password ?? "")
|
||||||
setRemoteAccessUrlInput(editingRemoteAccess?.url ?? "")
|
setRemoteAccessUrlInput(editingRemoteAccess?.url ?? "")
|
||||||
setRemoteAccessNotesInput(editingRemoteAccess?.notes ?? "")
|
setRemoteAccessNotesInput(editingRemoteAccess?.notes ?? "")
|
||||||
}, [remoteAccessDialog, editingRemoteAccess])
|
}, [remoteAccessDialog, editingRemoteAccess])
|
||||||
|
|
@ -3080,6 +3124,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
setRemoteAccessProviderOption(REMOTE_ACCESS_PROVIDERS[0].value)
|
setRemoteAccessProviderOption(REMOTE_ACCESS_PROVIDERS[0].value)
|
||||||
setRemoteAccessCustomProvider("")
|
setRemoteAccessCustomProvider("")
|
||||||
setRemoteAccessIdentifierInput("")
|
setRemoteAccessIdentifierInput("")
|
||||||
|
setRemoteAccessUsernameInput("")
|
||||||
|
setRemoteAccessPasswordInput("")
|
||||||
setRemoteAccessUrlInput("")
|
setRemoteAccessUrlInput("")
|
||||||
setRemoteAccessNotesInput("")
|
setRemoteAccessNotesInput("")
|
||||||
}
|
}
|
||||||
|
|
@ -3087,6 +3133,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowAllWindowsSoftware(false)
|
setShowAllWindowsSoftware(false)
|
||||||
|
setVisibleRemoteSecrets({})
|
||||||
}, [device?.id])
|
}, [device?.id])
|
||||||
|
|
||||||
const displayedWindowsSoftware = useMemo(
|
const displayedWindowsSoftware = useMemo(
|
||||||
|
|
@ -3149,6 +3196,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const username = remoteAccessUsernameInput.trim()
|
||||||
|
const password = remoteAccessPasswordInput.trim()
|
||||||
let normalizedUrl: string | undefined
|
let normalizedUrl: string | undefined
|
||||||
const rawUrl = remoteAccessUrlInput.trim()
|
const rawUrl = remoteAccessUrlInput.trim()
|
||||||
if (rawUrl.length > 0) {
|
if (rawUrl.length > 0) {
|
||||||
|
|
@ -3191,6 +3240,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
deviceId: device.id,
|
deviceId: device.id,
|
||||||
provider: providerName,
|
provider: providerName,
|
||||||
identifier,
|
identifier,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
url: normalizedUrl,
|
url: normalizedUrl,
|
||||||
notes: notes.length ? notes : undefined,
|
notes: notes.length ? notes : undefined,
|
||||||
action: "upsert",
|
action: "upsert",
|
||||||
|
|
@ -3220,6 +3271,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
remoteAccessProviderOption,
|
remoteAccessProviderOption,
|
||||||
remoteAccessCustomProvider,
|
remoteAccessCustomProvider,
|
||||||
remoteAccessIdentifierInput,
|
remoteAccessIdentifierInput,
|
||||||
|
remoteAccessUsernameInput,
|
||||||
|
remoteAccessPasswordInput,
|
||||||
remoteAccessUrlInput,
|
remoteAccessUrlInput,
|
||||||
remoteAccessNotesInput,
|
remoteAccessNotesInput,
|
||||||
editingRemoteAccess,
|
editingRemoteAccess,
|
||||||
|
|
@ -3337,6 +3390,41 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleCopyRemoteCredential = useCallback(async (value: string | null | undefined, label: string) => {
|
||||||
|
if (!value) return
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value)
|
||||||
|
toast.success(`${label} copiado.`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
toast.error(`Não foi possível copiar ${label.toLowerCase()}.`)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggleRemoteSecret = useCallback((clientId: string) => {
|
||||||
|
setVisibleRemoteSecrets((prev) => ({ ...prev, [clientId]: !prev[clientId] }))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleRustDeskConnect = useCallback((entry: DeviceRemoteAccessEntry) => {
|
||||||
|
if (!entry) return
|
||||||
|
const link = buildRustDeskUri(entry)
|
||||||
|
if (!link) {
|
||||||
|
toast.error("Não foi possível montar o link do RustDesk (ID ou senha ausentes).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
toast.error("A conexão direta só funciona no navegador.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
window.location.href = link
|
||||||
|
toast.success("Abrindo o RustDesk...")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
toast.error("Não foi possível acionar o RustDesk neste dispositivo.")
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Exportação individual (colunas personalizadas)
|
// Exportação individual (colunas personalizadas)
|
||||||
const [isSingleExportOpen, setIsSingleExportOpen] = useState(false)
|
const [isSingleExportOpen, setIsSingleExportOpen] = useState(false)
|
||||||
const [singleExporting, setSingleExporting] = useState(false)
|
const [singleExporting, setSingleExporting] = useState(false)
|
||||||
|
|
@ -3804,6 +3892,8 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
entry.lastVerifiedAt && Number.isFinite(entry.lastVerifiedAt)
|
entry.lastVerifiedAt && Number.isFinite(entry.lastVerifiedAt)
|
||||||
? new Date(entry.lastVerifiedAt)
|
? new Date(entry.lastVerifiedAt)
|
||||||
: null
|
: null
|
||||||
|
const isRustDesk = isRustDeskAccess(entry)
|
||||||
|
const secretVisible = Boolean(visibleRemoteSecrets[entry.clientId])
|
||||||
return (
|
return (
|
||||||
<div key={entry.clientId} className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-700">
|
<div key={entry.clientId} className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs sm:text-sm text-slate-700">
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
|
|
@ -3828,6 +3918,51 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{entry.username || entry.password ? (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{entry.username ? (
|
||||||
|
<div className="inline-flex flex-wrap items-center gap-2">
|
||||||
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Usuário</span>
|
||||||
|
<code className="rounded-md border border-slate-200 bg-white px-2 py-0.5 font-mono text-xs text-slate-700">
|
||||||
|
{entry.username}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 gap-1 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
|
||||||
|
onClick={() => handleCopyRemoteCredential(entry.username, "Usuário do acesso remoto")}
|
||||||
|
>
|
||||||
|
<ClipboardCopy className="size-3.5" /> Copiar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{entry.password ? (
|
||||||
|
<div className="inline-flex flex-wrap items-center gap-2">
|
||||||
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-slate-500">Senha</span>
|
||||||
|
<code className="rounded-md border border-slate-200 bg-white px-2 py-0.5 font-mono text-xs text-slate-700">
|
||||||
|
{secretVisible ? entry.password : "••••••••"}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 gap-1 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
|
||||||
|
onClick={() => toggleRemoteSecret(entry.clientId)}
|
||||||
|
>
|
||||||
|
{secretVisible ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
|
||||||
|
{secretVisible ? "Ocultar" : "Mostrar"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 gap-1 border border-transparent px-2 text-slate-600 hover:border-slate-300 hover:bg-slate-100 hover:text-slate-900"
|
||||||
|
onClick={() => handleCopyRemoteCredential(entry.password, "Senha do acesso remoto")}
|
||||||
|
>
|
||||||
|
<ClipboardCopy className="size-3.5" /> Copiar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{entry.url ? (
|
{entry.url ? (
|
||||||
<a
|
<a
|
||||||
href={entry.url}
|
href={entry.url}
|
||||||
|
|
@ -3838,6 +3973,16 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
Abrir console remoto
|
Abrir console remoto
|
||||||
</a>
|
</a>
|
||||||
) : null}
|
) : null}
|
||||||
|
{isRustDesk && (entry.identifier || entry.password) ? (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
className="mt-1 inline-flex items-center gap-2 bg-white/80 text-slate-800 hover:bg-white"
|
||||||
|
onClick={() => handleRustDeskConnect(entry)}
|
||||||
|
>
|
||||||
|
<MonitorSmartphone className="size-4" /> Conectar via RustDesk
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
{entry.notes ? (
|
{entry.notes ? (
|
||||||
<p className="whitespace-pre-wrap text-[11px] text-slate-600">{entry.notes}</p>
|
<p className="whitespace-pre-wrap text-[11px] text-slate-600">{entry.notes}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -4120,6 +4265,26 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label className="text-sm font-medium">Usuário (opcional)</label>
|
||||||
|
<Input
|
||||||
|
value={remoteAccessUsernameInput}
|
||||||
|
onChange={(event) => setRemoteAccessUsernameInput(event.target.value)}
|
||||||
|
placeholder="Ex: suporte@cliente.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label className="text-sm font-medium">Senha / PIN (opcional)</label>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={remoteAccessPasswordInput}
|
||||||
|
onChange={(event) => setRemoteAccessPasswordInput(event.target.value)}
|
||||||
|
placeholder="Senha permanente do RustDesk ou PIN"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Esse valor ficará disponível para os administradores do painel. Limpe o campo para remover a senha salva.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm font-medium">Link (opcional)</label>
|
<label className="text-sm font-medium">Link (opcional)</label>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -359,8 +359,19 @@ const PHYSICAL_DISK_COLUMN_WIDTHS = [22, 32, 18, 16, 16, 22, 16] as const
|
||||||
const NETWORK_HEADERS = ["Hostname", "Interface", "MAC", "IP", "Origem"] as const
|
const NETWORK_HEADERS = ["Hostname", "Interface", "MAC", "IP", "Origem"] as const
|
||||||
const NETWORK_COLUMN_WIDTHS = [22, 28, 22, 24, 18] as const
|
const NETWORK_COLUMN_WIDTHS = [22, 28, 22, 24, 18] as const
|
||||||
|
|
||||||
const REMOTE_ACCESS_HEADERS = ["Hostname", "Provedor", "Identificador", "URL", "Notas", "Última verificação", "Origem", "Metadados"] as const
|
const REMOTE_ACCESS_HEADERS = [
|
||||||
const REMOTE_ACCESS_COLUMN_WIDTHS = [22, 22, 24, 36, 28, 22, 16, 40] as const
|
"Hostname",
|
||||||
|
"Provedor",
|
||||||
|
"Identificador",
|
||||||
|
"Usuário",
|
||||||
|
"Senha",
|
||||||
|
"URL",
|
||||||
|
"Notas",
|
||||||
|
"Última verificação",
|
||||||
|
"Origem",
|
||||||
|
"Metadados",
|
||||||
|
] as const
|
||||||
|
const REMOTE_ACCESS_COLUMN_WIDTHS = [22, 22, 24, 24, 20, 32, 28, 22, 16, 36] as const
|
||||||
|
|
||||||
const SERVICE_HEADERS = ["Hostname", "Nome", "Exibição", "Status", "Origem"] as const
|
const SERVICE_HEADERS = ["Hostname", "Nome", "Exibição", "Status", "Origem"] as const
|
||||||
const SERVICE_COLUMN_WIDTHS = [22, 28, 36, 18, 18] as const
|
const SERVICE_COLUMN_WIDTHS = [22, 28, 36, 18, 18] as const
|
||||||
|
|
@ -756,6 +767,8 @@ function buildRemoteAccessRows(machines: MachineInventoryRecord[]): WorksheetRow
|
||||||
machine.hostname,
|
machine.hostname,
|
||||||
entry.provider ?? "—",
|
entry.provider ?? "—",
|
||||||
entry.identifier ?? "—",
|
entry.identifier ?? "—",
|
||||||
|
entry.username ?? "—",
|
||||||
|
entry.password ?? "—",
|
||||||
entry.url ?? "—",
|
entry.url ?? "—",
|
||||||
entry.notes ?? "—",
|
entry.notes ?? "—",
|
||||||
entry.lastVerifiedAt ? formatDateTime(entry.lastVerifiedAt) ?? "—" : "—",
|
entry.lastVerifiedAt ? formatDateTime(entry.lastVerifiedAt) ?? "—" : "—",
|
||||||
|
|
@ -1229,6 +1242,8 @@ function stringifyMetadata(metadata: Record<string, unknown> | null | undefined)
|
||||||
type RemoteAccessNormalized = {
|
type RemoteAccessNormalized = {
|
||||||
provider: string | null
|
provider: string | null
|
||||||
identifier: string | null
|
identifier: string | null
|
||||||
|
username: string | null
|
||||||
|
password: string | null
|
||||||
url: string | null
|
url: string | null
|
||||||
notes: string | null
|
notes: string | null
|
||||||
lastVerifiedAt: number | null
|
lastVerifiedAt: number | null
|
||||||
|
|
@ -1249,6 +1264,8 @@ function normalizeRemoteAccessEntry(
|
||||||
return {
|
return {
|
||||||
provider: providerHint ?? null,
|
provider: providerHint ?? null,
|
||||||
identifier: isUrl ? null : trimmed,
|
identifier: isUrl ? null : trimmed,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
url: isUrl ? trimmed : null,
|
url: isUrl ? trimmed : null,
|
||||||
notes: null,
|
notes: null,
|
||||||
lastVerifiedAt: null,
|
lastVerifiedAt: null,
|
||||||
|
|
@ -1273,6 +1290,19 @@ function normalizeRemoteAccessEntry(
|
||||||
ensureString(record["value"]) ??
|
ensureString(record["value"]) ??
|
||||||
ensureString(record["label"]) ??
|
ensureString(record["label"]) ??
|
||||||
null
|
null
|
||||||
|
const username =
|
||||||
|
ensureString(record["username"]) ??
|
||||||
|
ensureString(record["user"]) ??
|
||||||
|
ensureString(record["login"]) ??
|
||||||
|
ensureString(record["email"]) ??
|
||||||
|
ensureString(record["account"]) ??
|
||||||
|
null
|
||||||
|
const password =
|
||||||
|
ensureString(record["password"]) ??
|
||||||
|
ensureString(record["pass"]) ??
|
||||||
|
ensureString(record["secret"]) ??
|
||||||
|
ensureString(record["pin"]) ??
|
||||||
|
null
|
||||||
const url =
|
const url =
|
||||||
ensureString(record["url"]) ??
|
ensureString(record["url"]) ??
|
||||||
ensureString(record["link"]) ??
|
ensureString(record["link"]) ??
|
||||||
|
|
@ -1296,6 +1326,8 @@ function normalizeRemoteAccessEntry(
|
||||||
return {
|
return {
|
||||||
provider,
|
provider,
|
||||||
identifier,
|
identifier,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
url,
|
url,
|
||||||
notes,
|
notes,
|
||||||
lastVerifiedAt,
|
lastVerifiedAt,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ describe("normalizeDeviceRemoteAccess", () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
provider: null,
|
provider: null,
|
||||||
identifier: "PC-001",
|
identifier: "PC-001",
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
url: null,
|
url: null,
|
||||||
notes: null,
|
notes: null,
|
||||||
lastVerifiedAt: null,
|
lastVerifiedAt: null,
|
||||||
|
|
@ -25,6 +27,8 @@ describe("normalizeDeviceRemoteAccess", () => {
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
provider: null,
|
provider: null,
|
||||||
identifier: null,
|
identifier: null,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
url: "https://remote.example.com/session/123",
|
url: "https://remote.example.com/session/123",
|
||||||
notes: null,
|
notes: null,
|
||||||
lastVerifiedAt: null,
|
lastVerifiedAt: null,
|
||||||
|
|
@ -39,12 +43,16 @@ describe("normalizeDeviceRemoteAccess", () => {
|
||||||
code: "123-456-789",
|
code: "123-456-789",
|
||||||
remoteUrl: "https://anydesk.com/session/123",
|
remoteUrl: "https://anydesk.com/session/123",
|
||||||
note: "Suporte avançado",
|
note: "Suporte avançado",
|
||||||
|
user: "admin",
|
||||||
|
secret: "S3nh@",
|
||||||
verifiedAt: timestamp,
|
verifiedAt: timestamp,
|
||||||
extraTag: "vip",
|
extraTag: "vip",
|
||||||
})
|
})
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
provider: "AnyDesk",
|
provider: "AnyDesk",
|
||||||
identifier: "123-456-789",
|
identifier: "123-456-789",
|
||||||
|
username: "admin",
|
||||||
|
password: "S3nh@",
|
||||||
url: "https://anydesk.com/session/123",
|
url: "https://anydesk.com/session/123",
|
||||||
notes: "Suporte avançado",
|
notes: "Suporte avançado",
|
||||||
lastVerifiedAt: timestamp,
|
lastVerifiedAt: timestamp,
|
||||||
|
|
@ -53,6 +61,8 @@ describe("normalizeDeviceRemoteAccess", () => {
|
||||||
code: "123-456-789",
|
code: "123-456-789",
|
||||||
remoteUrl: "https://anydesk.com/session/123",
|
remoteUrl: "https://anydesk.com/session/123",
|
||||||
note: "Suporte avançado",
|
note: "Suporte avançado",
|
||||||
|
user: "admin",
|
||||||
|
secret: "S3nh@",
|
||||||
verifiedAt: timestamp,
|
verifiedAt: timestamp,
|
||||||
extraTag: "vip",
|
extraTag: "vip",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue