feat(desktop-agent,admin/inventory): secure token storage via keyring; extended inventory collectors per OS; new /api/machines/inventory endpoint; posture rules + tickets; Admin UI inventory with filters, search and export; docs + CI desktop release
This commit is contained in:
parent
c2050f311a
commit
479c66d52c
18 changed files with 1205 additions and 38 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { Store } from "@tauri-apps/plugin-store"
|
||||
import { getPassword, setPassword, deletePassword } from "@tauri-apps/plugin-keyring"
|
||||
|
||||
type MachineOs = {
|
||||
name: string
|
||||
|
|
@ -47,7 +48,6 @@ type MachineRegisterResponse = {
|
|||
|
||||
type AgentConfig = {
|
||||
machineId: string
|
||||
machineToken: string
|
||||
tenantId?: string | null
|
||||
companySlug?: string | null
|
||||
machineEmail?: string | null
|
||||
|
|
@ -70,7 +70,11 @@ declare global {
|
|||
}
|
||||
|
||||
const STORE_FILENAME = "machine-agent.json"
|
||||
const DEFAULT_APP_URL = "http://localhost:3000"
|
||||
// Defaults: em produção, apontamos para o domínio público; em dev, localhost
|
||||
const DEFAULT_APP_URL =
|
||||
import.meta.env.MODE === "production"
|
||||
? "https://tickets.esdrasrenan.com.br"
|
||||
: "http://localhost:3000"
|
||||
|
||||
function normalizeUrl(value?: string | null, fallback = DEFAULT_APP_URL) {
|
||||
const trimmed = (value ?? fallback).trim()
|
||||
|
|
@ -81,7 +85,10 @@ function normalizeUrl(value?: string | null, fallback = DEFAULT_APP_URL) {
|
|||
}
|
||||
|
||||
const appUrl = normalizeUrl(import.meta.env.VITE_APP_URL, DEFAULT_APP_URL)
|
||||
const apiBaseUrl = normalizeUrl(import.meta.env.VITE_API_BASE_URL, appUrl)
|
||||
const apiBaseUrl = normalizeUrl(
|
||||
import.meta.env.VITE_API_BASE_URL,
|
||||
appUrl
|
||||
)
|
||||
|
||||
const alertElement = document.getElementById("alert-container") as HTMLDivElement | null
|
||||
const contentElement = document.getElementById("content") as HTMLDivElement | null
|
||||
|
|
@ -107,6 +114,9 @@ function setStatus(message: string) {
|
|||
|
||||
let storeInstance: Store | null = null
|
||||
|
||||
const KEYRING_SERVICE = "sistema-de-chamados"
|
||||
const KEYRING_ACCOUNT = "machine-token"
|
||||
|
||||
async function ensureStoreLoaded(): Promise<Store> {
|
||||
if (!storeInstance) {
|
||||
try {
|
||||
|
|
@ -141,6 +151,24 @@ async function clearConfig() {
|
|||
const store = await ensureStoreLoaded()
|
||||
await store.delete("config")
|
||||
await store.save()
|
||||
try {
|
||||
await deletePassword({ service: KEYRING_SERVICE, account: KEYRING_ACCOUNT })
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function getMachineToken(): Promise<string | null> {
|
||||
try {
|
||||
const token = await getPassword({ service: KEYRING_SERVICE, account: KEYRING_ACCOUNT })
|
||||
return token && token.length > 0 ? token : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function setMachineToken(token: string) {
|
||||
await setPassword({ service: KEYRING_SERVICE, account: KEYRING_ACCOUNT, password: token })
|
||||
}
|
||||
|
||||
async function collectMachineProfile(): Promise<MachineProfile> {
|
||||
|
|
@ -148,9 +176,11 @@ async function collectMachineProfile(): Promise<MachineProfile> {
|
|||
}
|
||||
|
||||
async function startHeartbeat(config: AgentConfig) {
|
||||
const token = await getMachineToken()
|
||||
if (!token) throw new Error("Token da máquina ausente no cofre seguro")
|
||||
await invoke("start_machine_agent", {
|
||||
baseUrl: config.apiBaseUrl,
|
||||
token: config.machineToken,
|
||||
token,
|
||||
status: "online",
|
||||
intervalSeconds: 300,
|
||||
})
|
||||
|
|
@ -348,9 +378,10 @@ async function handleRegister(profile: MachineProfile, form: HTMLFormElement) {
|
|||
}
|
||||
|
||||
const data = (await response.json()) as MachineRegisterResponse
|
||||
// Guarda token com segurança no Keyring
|
||||
await setMachineToken(data.machineToken)
|
||||
const config: AgentConfig = {
|
||||
machineId: data.machineId,
|
||||
machineToken: data.machineToken,
|
||||
tenantId: data.tenantId ?? null,
|
||||
companySlug: data.companySlug ?? null,
|
||||
machineEmail: data.machineEmail ?? null,
|
||||
|
|
@ -387,8 +418,16 @@ async function handleRegister(profile: MachineProfile, form: HTMLFormElement) {
|
|||
}
|
||||
|
||||
function redirectToApp(config: AgentConfig) {
|
||||
const url = `${config.appUrl}/machines/handshake?token=${encodeURIComponent(config.machineToken)}`
|
||||
window.location.replace(url)
|
||||
const perform = async () => {
|
||||
const token = await getMachineToken()
|
||||
if (!token) {
|
||||
setAlert("Token da máquina não encontrado. Reprovisione a máquina.", "error")
|
||||
return
|
||||
}
|
||||
const url = `${config.appUrl}/machines/handshake?token=${encodeURIComponent(token)}`
|
||||
window.location.replace(url)
|
||||
}
|
||||
void perform()
|
||||
}
|
||||
|
||||
async function ensureHeartbeat(config: AgentConfig): Promise<AgentConfig> {
|
||||
|
|
@ -411,7 +450,8 @@ async function bootstrap() {
|
|||
|
||||
try {
|
||||
const stored = await loadConfig()
|
||||
if (stored?.machineToken) {
|
||||
const token = await getMachineToken()
|
||||
if (stored && token) {
|
||||
const updated = await ensureHeartbeat(stored)
|
||||
renderRegistered(updated)
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue