90 changed files with 8200 additions and 430 deletions
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
# Archive |
||||
|
||||
Folder ini berisi file yang tidak digunakan atau duplikat dari dokumentasi utama. |
||||
|
||||
## File yang Diarsipkan |
||||
|
||||
| File Asli | Lokasi Arsip | Alasan | |
||||
|-----------|--------------|--------| |
||||
| `collector/README.md` | `collector-README.md` | Tidak direferensikan di dokumentasi manapun | |
||||
| `infra/host.md` | `infra-host.md` | Duplikat dari `.cursor/server.md` | |
||||
|
||||
## Catatan |
||||
|
||||
File-file ini dipindahkan ke sini untuk menjaga struktur GeoNetAgent tetap rapi dan mudah dianalisa. Jika diperlukan kembali, file dapat dipindahkan kembali ke lokasi aslinya. |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
Mikrotik : |
||||
ssh -m hmac-sha1 admin@10.100.1.1 -p 255 |
||||
ssh -i "$env:USERPROFILE\.ssh\id_rsa_mikrotik" -o IdentitiesOnly=yes -m hmac-sha1 -p 255 admin@10.100.1.1 |
||||
|
||||
ssh -m hmac-sha1 admin@192.168.0.1 -p 255 |
||||
ssh -i "$env:USERPROFILE\.ssh\id_rsa_mikrotik" -o IdentitiesOnly=yes -m hmac-sha1 -p 255 admin@192.168.0.1 |
||||
|
||||
# Reverse_proxy: |
||||
ssh -m hmac-sha1 root@10.100.1.24 -p 22 |
||||
|
||||
# Main_Database: |
||||
ssh -m hmac-sha1 root@10.100.1.25 -p 22 |
||||
|
||||
Main_Webserver: |
||||
ssh -m hmac-sha1 Administrator@10.100.1.41 -p 22 |
||||
|
||||
AD-LDAP: |
||||
ssh -m hmac-sha1 Administrator@10.100.1.40 -p 22 |
||||
|
||||
zabbix: |
||||
ssh -m hmac-sha1 root@10.100.1.42 -p 22 |
||||
|
||||
|
||||
Ollama (aiopr, Ubuntu 22.04) — POC & inference CPU: |
||||
ssh ollama |
||||
.\scripts\ssh-ollama.ps1 |
||||
ssh -i "$env:USERPROFILE\.ssh\id_rsa_geonet_laptop" -o IdentitiesOnly=yes -m hmac-sha1 root@10.100.1.26 -p 22 |
||||
ssh -i "$env:USERPROFILE\.ssh\id_rsa_geonet_laptop" -o IdentitiesOnly=yes -m hmac-sha1 geonet@10.100.1.26 -p 22 |
||||
# Wajib PowerShell (bukan CMD) agar $env:USERPROFILE terbaca. Jangan pakai password — login key only. |
||||
# setup key geonet (sekali): type $env:USERPROFILE\.ssh\id_rsa_geonet_laptop.pub | ssh -m hmac-sha1 geonet@10.100.1.26 -p 22 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" |
||||
|
||||
# NAS QNAP (TS-932X): |
||||
ssh -i "$env:USERPROFILE\.ssh\id_ed25519" -o IdentitiesOnly=yes admin@10.100.1.10 -p 22 |
||||
.\scripts\ssh-qnap.ps1 |
||||
ssh -m hmac-sha1 admin@10.100.1.10 -p 22 |
||||
|
||||
|
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
# ADR 002 — Split Asset by Hardware Profile |
||||
|
||||
**Status:** Accepted |
||||
**Tanggal:** 2026-06-22 |
||||
**Fase:** 1 (Foundation) |
||||
|
||||
## Konteks |
||||
|
||||
Riwayat snapshot bisa tercampur pada satu asset jika disk OS dipindah antar mesin (hostname + `machine_id` sama, `smbios_uuid` beda) sebelum perbaikan merge rule (ADR 001). Contoh: `DESKTOP-RRJ9G01` — 18 snapshot laptop Dell + 23 snapshot PC Gigabyte. |
||||
|
||||
## Keputusan |
||||
|
||||
1. **Pencegahan (ingest):** Collector menolak merge jika `smbios_uuid` / serial hardware bertentangan — lihat ADR 001. |
||||
2. **Koreksi data historis:** `split_asset_by_hardware()` di `collector/app/services/asset_split_service.py` |
||||
- Kelompokkan snapshot by fingerprint: `smbios:{uuid}` atau `product:{manufacturer}|{model}` |
||||
- **Primary** = profil dengan snapshot terbaru — pertahankan `hostname` asli |
||||
- Profil lain → asset baru: `hostname-{smbios8}`, `display_name` = `{hostname} · Laptop|PC`, status `inactive` |
||||
- Relink `asset_snapshots` + `software_installations` |
||||
|
||||
## Operasional |
||||
|
||||
```bash |
||||
# Dry-run |
||||
docker exec geonet-collector python /app/scripts/split_asset_by_hardware.py <asset-uuid> --dry-run |
||||
|
||||
# Apply |
||||
docker exec geonet-collector python /app/scripts/split_asset_by_hardware.py <asset-uuid> |
||||
``` |
||||
|
||||
## Dampak |
||||
|
||||
- UI menampilkan dua baris asset (mis. `DESKTOP-RRJ9G01 · PC` + `DESKTOP-RRJ9G01 · Laptop`) |
||||
- Snapshot lama tidak hilang; masing-masing mengikuti hardware profile-nya |
||||
|
||||
## Referensi |
||||
|
||||
- `collector/scripts/split_asset_by_hardware.py` |
||||
- ADR 001 — merge rule disk clone |
||||
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
# ADR 003 — Dokumen Asset & Tiket di QNAP Object Storage |
||||
|
||||
**Status:** Accepted |
||||
**Tanggal:** 2026-06-22 |
||||
**Fase:** 3 (Infra sync & CMDB) — desain disetujui; implementasi portal belum |
||||
|
||||
## Konteks |
||||
|
||||
IT perlu melampirkan file non-struktur ke CMDB: |
||||
|
||||
| Domain | Contoh file | |
||||
|--------|-------------| |
||||
| **Procurement** | PDF invoice, PO, foto unit, garansi | |
||||
| **Service** | BA perbaikan, foto kerusakan, bukti spare part | |
||||
| **Complain** | Form keluhan, screenshot error, foto kondisi dari user | |
||||
|
||||
File biner **tidak** disimpan di PostgreSQL. Infrastruktur existing: **QNAP TS-932X** (`10.100.1.10`) dengan **Object Storage** (S3-compatible API) sudah ready (`project-context.mdc`). |
||||
|
||||
Identitas hardware (agent) dan dokumen (manual IT/user) harus terpisah: |
||||
|
||||
- **Agent** → hardware snapshot, merge via ADR 001 |
||||
- **Portal** → upload dokumen, bind ke `assets.id` atau tiket |
||||
|
||||
## Keputusan |
||||
|
||||
### 1. Pola penyimpanan |
||||
|
||||
``` |
||||
[Laravel Portal] ──upload──► QNAP S3 bucket |
||||
│ |
||||
└── metadata ──► PostgreSQL (invoice / ticket / document_attachments) |
||||
``` |
||||
|
||||
| Lapisan | Peran | |
||||
|---------|--------| |
||||
| **QNAP Object Storage** | File biner (PDF, JPG, PNG, …) | |
||||
| **PostgreSQL** | Metadata, relasi, audit, workflow tiket | |
||||
| **FastAPI collector** | **Tidak** menulis file; tetap ingress agent saja | |
||||
| **Laravel portal** | Upload, download (presigned URL atau proxy), CRUD | |
||||
|
||||
### 2. Tiga domain data |
||||
|
||||
| Domain | Tabel utama | Lampiran via | |
||||
|--------|-------------|--------------| |
||||
| Procurement | `asset_invoices` | `document_attachments` (`invoice_id`) | |
||||
| Service | `tickets` (`ticket_type = service`) | `document_attachments` (`ticket_id`) | |
||||
| Complain | `tickets` (`ticket_type = complaint`) | `document_attachments` (`ticket_id`) | |
||||
|
||||
Procurement punya tabel terstruktur (nomor invoice, harga, vendor) karena field finansial. Service & complain berbagi **satu tabel tiket** + workflow status. |
||||
|
||||
### 3. Tabel `document_attachments` (bersama) |
||||
|
||||
Satu tabel untuk semua file di object storage. Constraint: minimal satu FK parent (`asset_id`, `invoice_id`, atau `ticket_id`). |
||||
|
||||
| Kolom penting | Keterangan | |
||||
|---------------|------------| |
||||
| `storage_provider` | `qnap_s3` (default) | |
||||
| `bucket`, `object_key` | Lokasi di object storage | |
||||
| `domain` | `procurement` \| `service` \| `complaint` | |
||||
| `kind` | Semantik file — lihat tabel di bawah | |
||||
| `original_filename`, `content_type`, `file_size_bytes` | Metadata file | |
||||
| `checksum_sha256` | Opsional — deduplikasi / integritas | |
||||
| `uploaded_by`, `uploaded_at` | Audit | |
||||
| `deleted_at` | Soft delete (file di S3 diarsip/hapus terpisah) | |
||||
|
||||
**Nilai `kind` (awal):** |
||||
|
||||
| kind | Domain | |
||||
|------|--------| |
||||
| `invoice_pdf` | procurement | |
||||
| `po_pdf` | procurement | |
||||
| `unit_photo` | procurement | |
||||
| `warranty_doc` | procurement | |
||||
| `service_report` | service | |
||||
| `service_photo` | service | |
||||
| `parts_receipt` | service | |
||||
| `complaint_form` | complaint | |
||||
| `complaint_photo` | complaint | |
||||
| `complaint_screenshot` | complaint | |
||||
| `other` | semua | |
||||
|
||||
Lampiran bisa juga mengacu langsung ke `asset_id` tanpa invoice/tiket (mis. foto unit umum). |
||||
|
||||
### 4. Struktur object key (QNAP) |
||||
|
||||
Bucket: `geonetagent-documents` (buat sekali di QNAP). |
||||
|
||||
``` |
||||
{domain}/{yyyy}/{mm}/{attachment_id}/{sanitized_original_filename} |
||||
``` |
||||
|
||||
Contoh: |
||||
|
||||
``` |
||||
procurement/2026/06/8f3a2c1e-....-uuid/INV-2024-00123.pdf |
||||
service/2026/06/9a1b....../ba-service-signed.pdf |
||||
complaint/2026/06/7c2d....../screenshot-error.png |
||||
``` |
||||
|
||||
`attachment_id` = UUID baris `document_attachments` — hindari bentrok nama file. |
||||
|
||||
### 5. Pengikat ke Laptop/PC (tidak berubah saat reinstall / upgrade komponen) |
||||
|
||||
| Pengikat dokumen | Stabil reinstall? | Stabil ganti CPU/GPU/RAM/disk? | |
||||
|------------------|-------------------|--------------------------------| |
||||
| **`assets.id`** (UUID CMDB) | ✅ | ✅ — **anchor utama invoice & tiket** | |
||||
| `asset_invoices.asset_id` | ✅ | ✅ | |
||||
| `tickets.asset_id` | ✅ | ✅ | |
||||
| `serial_number` / `smbios_uuid` | Verifikasi agent (ADR 001) | Berubah jika motherboard diganti | |
||||
|
||||
Dokumen **tidak** dikait ke `cpu_model`, `gpu`, atau `physical_disks` — itu inventori snapshot, bukan identitas asset. |
||||
|
||||
### 6. Akses & keamanan |
||||
|
||||
| Aturan | Detail | |
||||
|--------|--------| |
||||
| Endpoint S3 | LAN internal (`10.100.1.10`); tidak expose publik | |
||||
| Kredensial | IAM/bucket user khusus `geonetagent-portal` — least privilege | |
||||
| Download | Presigned URL TTL pendek (15–60 menit) atau stream via Laravel | |
||||
| Complain | User hanya lihat tiket/file milik sendiri; IT lihat semua | |
||||
| Ukuran upload | Batas aplikasi: PDF ≤ 10 MB, gambar ≤ 5 MB (konfigurasi portal) | |
||||
| Retensi | Kebijakan IT (mis. complain 2 th, service 5 th) — fase implementasi | |
||||
|
||||
### 7. Batas MVP implementasi |
||||
|
||||
- **Fase 3** — migration DB + Laravel upload/download |
||||
- Collector UI (`/ui/`) tetap **read-only**; tidak upload file |
||||
- Agent PowerShell **tidak** mengunggah dokumen |
||||
- `portal_users` (auth IT) prerequisite untuk upload |
||||
|
||||
## Alternatif yang ditolak |
||||
|
||||
| Alternatif | Alasan ditolak | |
||||
|------------|----------------| |
||||
| File di kolom `bytea` PostgreSQL | DB membengkak, backup lambat | |
||||
| Satu folder SMB share tanpa metadata | Sulit audit, link ke asset, permission | |
||||
| Tabel lampiran terpisah per domain | Duplikasi schema; satu `document_attachments` cukup | |
||||
| Tiket terpisah `service_tickets` + `complaints` | Workflow sama; cukup `ticket_type` | |
||||
|
||||
## Dampak |
||||
|
||||
- Migration: `infra/db/migrations/005_asset_documents.sql` (draft — jalankan saat portal scaffold) |
||||
- Schema: `.cursor/db-schema.md` — tabel `asset_invoices`, `tickets`, `document_attachments` |
||||
- API kontrak: endpoint upload Laravel (bukan FastAPI agent) — `api-contracts.md` § portal (TODO) |
||||
- Infra: bucket QNAP + env `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY` di portal `.env` |
||||
|
||||
## Diagram |
||||
|
||||
```mermaid |
||||
flowchart TB |
||||
subgraph portal [Laravel Portal] |
||||
UP[Upload handler] |
||||
UI[Asset / Ticket UI] |
||||
end |
||||
subgraph storage [QNAP 10.100.1.10] |
||||
S3[(Object Storage bucket)] |
||||
end |
||||
subgraph db [PostgreSQL main-db] |
||||
AST[assets] |
||||
INV[asset_invoices] |
||||
TIX[tickets] |
||||
ATT[document_attachments] |
||||
end |
||||
subgraph agent [Agent - unchanged] |
||||
AG[POST report] |
||||
end |
||||
UP --> S3 |
||||
UP --> ATT |
||||
INV --> AST |
||||
TIX --> AST |
||||
ATT --> INV |
||||
ATT --> TIX |
||||
ATT --> AST |
||||
AG --> AST |
||||
UI --> ATT |
||||
``` |
||||
|
||||
## Referensi |
||||
|
||||
- ADR 001 — identitas hardware agent |
||||
- `.cursor/db-schema.md` — definisi tabel |
||||
- `infra/db/migrations/005_asset_documents.sql` |
||||
- `infra/host.md` — SSH QNAP |
||||
- View hardware: `v_asset_hardware_latest` (migration 004) |
||||
|
||||
## Checklist implementasi (nanti) |
||||
|
||||
- [ ] Buat bucket `geonetagent-documents` + user S3 di QNAP |
||||
- [ ] Jalankan migration 005 di `geonetagent_dev` |
||||
- [ ] Laravel: model + storage driver S3-compatible |
||||
- [ ] UI: tab Dokumen di detail asset; form tiket service/complain |
||||
- [ ] Presigned download + audit log |
||||
@ -0,0 +1,495 @@
@@ -0,0 +1,495 @@
|
||||
# Context Index (AI Entry Point) |
||||
|
||||
> **Versi Dokumen:** 1.0.0 |
||||
> **Terakhir Diperbarui:** 2026-06-23 |
||||
> **Status:** Aktif |
||||
> **Pemilik:** Tim Pengembang GeoNetAgent |
||||
|
||||
--- |
||||
|
||||
# Tujuan |
||||
|
||||
Dokumen ini merupakan **titik masuk utama (Single Entry Point)** bagi seluruh AI Agent dan Developer GeoNetAgent. |
||||
|
||||
Setiap AI **WAJIB** membaca dokumen ini terlebih dahulu sebelum melakukan analisis, implementasi, perubahan kode, refactoring, penghapusan, deployment, maupun pembuatan dokumentasi. |
||||
|
||||
Dokumen ini **bukan** tempat menyimpan seluruh informasi proyek, melainkan **peta navigasi** menuju seluruh dokumentasi yang tersedia. |
||||
|
||||
--- |
||||
|
||||
# Filosofi Proyek |
||||
|
||||
Proyek ini menerapkan prinsip **AI Native Development**. |
||||
|
||||
Artinya: |
||||
|
||||
* Dokumentasi adalah bagian dari source code. |
||||
* Setiap perubahan wajib terdokumentasi. |
||||
* Repository merupakan sumber kebenaran utama (Single Source of Truth). |
||||
* AI tidak boleh mengandalkan riwayat percakapan. |
||||
* Seluruh pengetahuan proyek harus tersimpan dalam repository. |
||||
* Developer sering berpindah perangkat (PC, Laptop, Workstation) — AI Agent harus tetap bisa melanjutkan tanpa riwayat chat. |
||||
|
||||
--- |
||||
|
||||
# Aturan Utama AI |
||||
|
||||
Sebelum melakukan pekerjaan apa pun, AI wajib: |
||||
|
||||
1. Membaca file ini (`context-index.md`). |
||||
2. Membaca dokumentasi yang berkaitan dengan tugas. |
||||
3. Memahami arsitektur yang ada. |
||||
4. Memastikan tidak ada konflik dengan keputusan arsitektur sebelumnya (cek ADR). |
||||
5. Memperbarui dokumentasi setelah implementasi selesai. |
||||
6. Mengikuti protokol sync di `perintah.md` sebelum menyatakan pekerjaan selesai. |
||||
|
||||
Implementasi tanpa dokumentasi dianggap **belum selesai**. |
||||
|
||||
--- |
||||
|
||||
# Urutan Membaca Dokumentasi |
||||
|
||||
AI wajib membaca dokumen sesuai urutan berikut: |
||||
|
||||
1. `context-index.md` (file ini) |
||||
2. `blueprint.md` — Master blueprint, visi, arsitektur, modul |
||||
3. `progress.md` — Fase aktif & snapshot progress |
||||
4. Dokumen spesifik modul (lihat tabel Struktur Dokumentasi di bawah) |
||||
5. `agent-playbook.md` — Workflow multi-tab & koordinasi |
||||
6. `perintah.md` — Protokol sync dokumentasi (wajib akhir sesi) |
||||
|
||||
Jangan membaca seluruh repository apabila tidak diperlukan. |
||||
|
||||
--- |
||||
|
||||
# Struktur Dokumentasi |
||||
|
||||
## 1. Blueprint |
||||
|
||||
**Lokasi:** `.cursor/blueprint.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Visi produk & masalah yang diselesaikan |
||||
* Non-goals (sengaja TIDAK dibangun) |
||||
* Boundary System — ownership matrix (Zabbix, FastAPI, Laravel) |
||||
* Alur data utama (agent, Zabbix, Proxmox, user dashboard) |
||||
* Modul aplikasi & prioritas (M1-M9) |
||||
* Keputusan teknis locked (PostgreSQL, monorepo, API versioning) |
||||
* Entity domain ringkas |
||||
* Integrasi eksternal (Zabbix, Proxmox, MikroTik, QNAP, AD) |
||||
* Security blueprint |
||||
* Deployment target |
||||
* Roadmap & Definition of Done per fase |
||||
* Glossarium |
||||
|
||||
**Baca saat:** Selalu — cek visi & batas modul sebelum coding. |
||||
|
||||
--- |
||||
|
||||
## 2. Progress |
||||
|
||||
**Lokasi:** `.cursor/progress.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Snapshot status (fase aktif, agent version, collector status) |
||||
* Progress ringkas per fase (0-4) |
||||
* Checklist detail per fase dengan DoD (Definition of Done) |
||||
* Agent feature log (versi & fitur per versi) |
||||
* Backlog agent (fitur yang belum diimplementasi) |
||||
* Riwayat milestone (tanggal & milestone penting) |
||||
|
||||
**Baca saat:** Selalu — cek fase aktif sebelum coding. Jangan implement fase 3 saat fase 1 belum DoD. |
||||
|
||||
--- |
||||
|
||||
## 3. Agent Playbook |
||||
|
||||
**Lokasi:** `.cursor/agent-playbook.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Prinsip koordinasi multi-tab Cursor Agent |
||||
* Workflow multi-tab (DB, API, UI, Infra, Zabbix) |
||||
* Assignment tab → scope & folder |
||||
* Checklist sebelum & sesudah coding |
||||
* Dokumen — kapan diupdate (mapping event → file) |
||||
* Release agent protocol (publish + deploy + milestone log) |
||||
* Handling deviasi blueprint |
||||
* Conflict prevention |
||||
* Prompt template untuk user |
||||
* Status tracking |
||||
* Protokol sync dokumentasi |
||||
|
||||
**Baca saat:** Saat paralel development atau koordinasi antar tab. |
||||
|
||||
--- |
||||
|
||||
## 4. Database Schema |
||||
|
||||
**Lokasi:** `.cursor/db-schema.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Konvensi (PK UUID, timestamptz, snake_case) |
||||
* ERD (Mermaid) |
||||
* Tabel detail (`locations`, `assets`, `asset_snapshots`, `asset_disks`, `users`, `software_installations`, `licenses`, `asset_invoices`, `tickets`, `document_attachments`, `alerts`, `agent_tokens`, `sync_logs`) |
||||
* View `v_asset_hardware_latest` |
||||
* Laravel auth tables (`portal_users`) |
||||
* Sample queries |
||||
* Migration order |
||||
|
||||
**Baca saat:** Saat kerja database/migration, schema change, atau query data. |
||||
|
||||
--- |
||||
|
||||
## 5. API Contracts |
||||
|
||||
**Lokasi:** `.cursor/api-contracts.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Overview service & base URL |
||||
* FastAPI — Agent Ingress (`POST /api/v1/agent/report`) |
||||
* FastAPI — Read API CMDB (GET assets, snapshots, software) |
||||
* FastAPI — Proxmox sync (`POST /api/v1/sync/proxmox`) |
||||
* Laravel — Portal API (pages, asset documents, tickets) |
||||
* Laravel — Zabbix Webhook (`POST /webhooks/zabbix`) |
||||
* Laravel → Zabbix API (read-only host status) |
||||
* Proxmox API (FastAPI sync job) |
||||
* PowerShell Agent — Local Contract |
||||
* Error format standar |
||||
* Versioning policy |
||||
|
||||
**Baca saat:** Saat kerja backend/API, integrasi agent, atau webhook. |
||||
|
||||
--- |
||||
|
||||
## 6. Repository Structure |
||||
|
||||
**Lokasi:** `.cursor/repo-structure.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Root layout (`.cursor/`, `agent/`, `collector/`, `portal/`, `infra/`, `samples/`) |
||||
* Keputusan migration DB (Alembic vs Laravel) |
||||
* Naming conventions (Python, PHP, PowerShell) |
||||
* Environment variables |
||||
* Docker Compose dev |
||||
* Git branch strategy |
||||
* File ownership per agent tab |
||||
* README.md root target content |
||||
|
||||
**Baca saat:** Saat scaffold/buat file baru atau struktur folder. |
||||
|
||||
--- |
||||
|
||||
## 7. Profil & Infrastruktur |
||||
|
||||
**Lokasi:** `.cursor/me.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Profil user (IT end-to-end: network, server, storage, GIS, licensing) |
||||
* Stack & aset yang sudah dimiliki (Laravel, FastAPI, Zabbix, AD, Proxmox, QNAP, MikroTik) |
||||
* Lingkungan development (workspace, OS dev, contoh endpoint) |
||||
* Ruang lingkup tanggung jawab |
||||
* Preferensi kerja (Bahasa Indonesia, jangan duplikasi Zabbix, PostgreSQL untuk baru) |
||||
* File konteks terkait |
||||
* Status proyek |
||||
* URL operasional |
||||
|
||||
**Ringkasan Profil User:** |
||||
|
||||
User adalah **IT Administrator** yang mengelola infrastruktur end-to-end: |
||||
|
||||
- **Network & Security:** MikroTik CCR2004 (gateway, routing, firewall) |
||||
- **Compute & Virtualization:** Proxmox (VM host), PostgreSQL/SQL Server (database), Docker (containers) |
||||
- **Identity:** Windows Server 2012 R2 Active Directory (domain user management, GPO deploy) |
||||
- **Storage:** QNAP TS-932X (NAS, Object Storage untuk dokumen) |
||||
- **Monitoring:** Zabbix (alerting real-time) |
||||
- **Endpoint:** Banyak laptop user (Windows, domain-joined) |
||||
- **GIS & Licensing:** Software licensing tracking |
||||
|
||||
**Preferensi Kerja:** |
||||
|
||||
- Bahasa komunikasi: **Bahasa Indonesia** |
||||
- Jangan duplikasi fungsi Zabbix — Zabbix tetap pusat alerting real-time |
||||
- PostgreSQL untuk aplikasi/CMDB baru; SQL Server untuk legacy/GIS |
||||
- Minimalkan scope perubahan; ikuti konvensi existing code |
||||
- Jangan commit kecuali diminta eksplisit |
||||
- Sering berpindah perangkat (PC, Laptop, Workstation) — gunakan `.cursor/` sebagai shared memory |
||||
|
||||
**Baca saat:** Selalu — konteks lingkungan & preferensi user. |
||||
|
||||
--- |
||||
|
||||
## 8. Server & SSH |
||||
|
||||
**Lokasi:** `.cursor/server.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* SSH commands ke seluruh server (MikroTik, reverse proxy, main database, webserver, AD-LDAP, Zabbix, Ollama, QNAP) |
||||
* Port & credential hints |
||||
* Service per server |
||||
|
||||
**Baca saat:** Saat deploy / akses infra. |
||||
|
||||
--- |
||||
|
||||
## 9. Architecture Decision Records (ADR) |
||||
|
||||
**Lokasi:** `.cursor/adr/` |
||||
|
||||
**Berisi:** |
||||
|
||||
* `001-agent-asset-identity.md` — Strategi identitas asset (serial → smbios_uuid → MAC → machine_id → hostname) |
||||
* `002-asset-split-by-hardware.md` — Split asset dengan hardware campur (laptop + PC dalam 1 chassis) |
||||
* `003-asset-documents-qnap.md` — Dokumen procurement/service/complain di QNAP Object Storage |
||||
|
||||
**Baca saat:** Saat keputusan desain baru atau implementasi fitur yang terdampak ADR. |
||||
|
||||
**Catatan:** ADR tidak boleh dihapus. Jika berubah, buat ADR baru. |
||||
|
||||
--- |
||||
|
||||
## 10. Perintah (Sync Protocol) |
||||
|
||||
**Lokasi:** `perintah.md` (root repo) |
||||
|
||||
**Berisi:** |
||||
|
||||
* Indeks cepat operasional (progress, SSH, API, QNAP, install agent, nginx, deploy) |
||||
* URL produksi (domain `gisportal.id`) |
||||
* Install agent (PowerShell commands) |
||||
* Admin — deploy & DB commands |
||||
* Agent versi saat ini |
||||
* Pertanyaan sync (checklist AI wajib akhir sesi) |
||||
* Sync terakhir (changelog singkat) |
||||
|
||||
**Baca saat:** Akhir sesi / sebelum nyatakan done (wajib). |
||||
|
||||
--- |
||||
|
||||
## 11. Rules |
||||
|
||||
**Lokasi:** `.cursor/rules/` |
||||
|
||||
**Berisi:** |
||||
|
||||
* `project-context.mdc` — Rule Cursor: muat konteks proyek otomatis |
||||
* Rules tambahan (coding standard, security, dll) — akan ditambah sesuai kebutuhan |
||||
|
||||
**Baca saat:** Saat perlu rules spesifik atau konvensi coding. |
||||
|
||||
--- |
||||
|
||||
## 12. Monitoring Architecture |
||||
|
||||
**Lokasi:** `.cursor/monitoring-architecture.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Layer Zabbix/FastAPI/Laravel |
||||
* DXDiag integration |
||||
* Roadmap fase monitoring |
||||
|
||||
**Baca saat:** Saat kerja monitoring/integrasi Zabbix. |
||||
|
||||
--- |
||||
|
||||
## 13. Collector API Documentation |
||||
|
||||
**Lokasi:** `collector/docs/API.md` |
||||
|
||||
**Berisi:** |
||||
|
||||
* Panduan konsumsi Collector API |
||||
* Contoh curl/PowerShell/Python |
||||
* Detail endpoint & response |
||||
|
||||
**Baca saat:** Integrasi agent / aplikasi lain ke collector. |
||||
|
||||
--- |
||||
|
||||
# Informasi Infrastruktur |
||||
|
||||
Seluruh informasi server berada di `.cursor/server.md`. |
||||
|
||||
## Audit Server (OS & Aplikasi) |
||||
|
||||
| Server | IP | OS / Versi | Digunakan di GeoNetAgent? | Fungsi | |
||||
|--------|-----|------------|---------------------------|--------| |
||||
| MikroTik CCR2004 | 10.100.1.1 / 192.168.0.1 | RouterOS | ☐ | Gateway, routing, firewall | |
||||
| Reverse Proxy (nginx) | 10.100.1.24 | CentOS Linux 8 | ☑ YES | GeoNetAgent collector, agent.gisportal.id, collector.gisportal.id | |
||||
| Main Database | 10.100.1.25 | Ubuntu 18.04.6 LTS | ☑ YES | PostgreSQL 12 — `geonetagent_dev` | |
||||
| Main Webserver | 10.100.1.41 | Windows Server 2012 R2 (6.3.9600) | ☐ | Web server | |
||||
| AD-LDAP | 10.100.1.40 | Windows Server 2012 R2 (6.3.9600) | ☐ | Active Directory (GPO deploy agent - fase 2) | |
||||
| Zabbix | 10.100.1.42 | Ubuntu 22.04.1 LTS | ☐ | Monitoring Server (integrasi webhook - fase 2) | |
||||
| Ollama | 10.100.1.26 | Ubuntu 22.04.1 LTS | ☐ | AI inference (opsional - fase 4) | |
||||
| QNAP TS-932X | 10.100.1.10 | QTS 5.2.9 (ARM64) | ☑ YES | NAS / Object Storage (dokumen ADR 003) | |
||||
|
||||
**Catatan:** |
||||
- ☑ = Sudah dikonfirmasi digunakan di GeoNetAgent |
||||
- ☐ = Belum dikonfirmasi atau belum digunakan di fase saat ini |
||||
- User dapat mengisi checkbox ini sesuai kebutuhan |
||||
|
||||
AI tidak boleh mengubah konfigurasi server tanpa memperbarui dokumentasi. |
||||
|
||||
--- |
||||
|
||||
# Informasi API |
||||
|
||||
Semua API wajib memiliki dokumentasi di `.cursor/api-contracts.md`. |
||||
|
||||
**FastAPI Collector:** |
||||
|
||||
* Base URL dev: `http://localhost:8000` |
||||
* Base URL prod: `https://collector.gisportal.id` |
||||
* Auth: Bearer token |
||||
* Endpoint: `POST /api/v1/agent/report`, GET `/api/v1/assets`, dll |
||||
|
||||
**Laravel Portal:** |
||||
|
||||
* Base URL dev: `http://localhost:8080` |
||||
* Base URL prod: `https://portal.geonet.internal` |
||||
* Auth: Session / Sanctum |
||||
|
||||
--- |
||||
|
||||
# Informasi Database |
||||
|
||||
Seluruh perubahan database wajib memperbarui `.cursor/db-schema.md`. |
||||
|
||||
**Database:** `geonetagent` (prod) · `geonetagent_dev` (dev/testing) |
||||
|
||||
**Engine:** PostgreSQL 12+ |
||||
|
||||
**Ownership:** FastAPI = write ingress otomatis | Laravel = read + manual CRUD terbatas |
||||
|
||||
**Migration tool:** Alembic (core schema) + Laravel migrations (portal-specific) |
||||
|
||||
Tidak boleh ada perubahan database tanpa dokumentasi. |
||||
|
||||
--- |
||||
|
||||
# Checklist Sebelum Implementasi |
||||
|
||||
AI wajib memastikan: |
||||
|
||||
☐ Sudah membaca `context-index.md` |
||||
|
||||
☐ Sudah membaca `blueprint.md` — cek fase & DoD |
||||
|
||||
☐ Sudah membaca `progress.md` — cek fase aktif |
||||
|
||||
☐ Sudah membaca dokumentasi spesifik modul (db-schema, api-contracts, dll) |
||||
|
||||
☐ Sudah memahami ADR yang relevan |
||||
|
||||
☐ Sudah memahami infrastruktur di `me.md` & `server.md` |
||||
|
||||
☐ Sudah memastikan dependency tab lain sudah ada di disk (jika paralel) |
||||
|
||||
--- |
||||
|
||||
# Checklist Setelah Implementasi |
||||
|
||||
AI wajib memperbarui: |
||||
|
||||
☐ `progress.md` — centang item selesai, update snapshot, tambahkan milestone jika perlu |
||||
|
||||
☐ ADR baru (jika ada perubahan arsitektur/desain) |
||||
|
||||
☐ Dokumentasi spesifik (db-schema.md, api-contracts.md, repo-structure.md, dll) |
||||
|
||||
☐ `blueprint.md` — jika modul/fase baru |
||||
|
||||
☐ `agent-playbook.md` — jika workflow berubah |
||||
|
||||
☐ `perintah.md` — sync terakhir (wajib akhir sesi) |
||||
|
||||
☐ `me.md` — jika infrastruktur berubah |
||||
|
||||
☐ `context-index.md` — jika ada dokumen baru |
||||
|
||||
☐ Deploy agent (jika bump versi): `publish_agent_install.ps1` + `push_collector.ps1` |
||||
|
||||
--- |
||||
|
||||
# Prinsip Pengembangan |
||||
|
||||
Setiap perubahan harus memenuhi prinsip berikut: |
||||
|
||||
* Mudah dipahami developer baru. |
||||
* Mudah dipahami AI Agent lain (tanpa riwayat chat). |
||||
* Mudah diaudit. |
||||
* Mudah di-maintain. |
||||
* Memiliki rollback plan. |
||||
* Memiliki dokumentasi lengkap. |
||||
* Tidak menghasilkan technical debt yang tidak terdokumentasi. |
||||
* Mengikuti fase di `progress.md` — jangan loncat fase. |
||||
* Repository adalah sumber kebenaran utama. |
||||
|
||||
--- |
||||
|
||||
# Quick Reference |
||||
|
||||
## URL Produksi |
||||
|
||||
| Fungsi | URL | |
||||
|--------|-----| |
||||
| Agent POST (internet) | `https://agent.gisportal.id/api/v1/agent/report` | |
||||
| Install / update agent | `https://agent.gisportal.id/install/` | |
||||
| UI + read API HTTPS | `https://collector.gisportal.id/ui/` | |
||||
| UI + read API HTTP LAN | `http://10.100.1.24:8000/ui/` | |
||||
|
||||
## Agent Version |
||||
|
||||
**Current:** `v0.1.10-phase0` — `cpu{}` detail, `physical_disks[]`, RAM/GPU detail, `system.form_factor`, auto-update via manifest. |
||||
|
||||
## Fase Aktif |
||||
|
||||
**Fase 1 — Foundation** 🔄 |
||||
|
||||
DoD: Agent di 1 laptop test POST → data di DB → Laravel tampilkan halaman asset list. |
||||
|
||||
Progress: 8 / 9 item selesai. Blocker: Laravel asset index belum scaffold. |
||||
|
||||
## Deploy Commands |
||||
|
||||
```powershell |
||||
# Publish agent |
||||
.\infra\collector\publish_agent_install.ps1 |
||||
|
||||
# Deploy collector |
||||
.\infra\collector\push_collector.ps1 |
||||
|
||||
# Deploy nginx |
||||
infra/collector/deploy_nginx.sh |
||||
``` |
||||
|
||||
--- |
||||
|
||||
# Penutup |
||||
|
||||
Repository ini dirancang agar: |
||||
|
||||
* Developer baru dapat memahami proyek dengan cepat. |
||||
* AI Agent mana pun dapat melanjutkan pekerjaan tanpa kehilangan konteks. |
||||
* Seluruh pengetahuan proyek tersimpan di dalam repository. |
||||
* Dokumentasi selalu selaras dengan implementasi. |
||||
|
||||
Repository adalah sumber kebenaran utama. |
||||
|
||||
Jika terjadi perbedaan antara implementasi dan dokumentasi, AI wajib memperbarui dokumentasi hingga keduanya konsisten. |
||||
|
||||
--- |
||||
|
||||
# Bahasa & Gaya |
||||
|
||||
* Komunikasi dengan user: **Bahasa Indonesia** |
||||
* Kode & komentar: **English** (konvensi industri) |
||||
* Dokumentasi `.cursor/`: **Bahasa Indonesia** (kecuali istilah teknis) |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
--- |
||||
description: Wajib publish agent + deploy collector/UI + catat milestone saat bump versi agent |
||||
alwaysApply: true |
||||
--- |
||||
|
||||
# Agent Release — Deploy Server & Dokumentasi |
||||
|
||||
Setiap perubahan agent yang **mem-bump versi** (`agent/VERSION`, `manifest.json`, atau string `AgentVersion` di skrip) **belum selesai** sampai ketiga langkah ini dilakukan. |
||||
|
||||
## 1. Publish agent ke server (wajib) |
||||
|
||||
Endpoint user/auto-update membaca `https://agent.gisportal.id/install/manifest.json`. Tanpa publish, installer dan `UserAgent.ps1` tetap mengunduh versi lama. |
||||
|
||||
```powershell |
||||
.\infra\collector\publish_agent_install.ps1 |
||||
``` |
||||
|
||||
Verifikasi: manifest HTTPS = versi baru; jumlah file manifest sesuai (mis. `Get-CpuInfo.ps1` ikut ter-upload). |
||||
|
||||
## 2. Deploy collector + UI (wajib jika ada perubahan payload/schema/UI) |
||||
|
||||
Perubahan payload, schema FastAPI, `asset_read_service`, atau `collector/static/` **wajib** rebuild container: |
||||
|
||||
```powershell |
||||
.\infra\collector\push_collector.ps1 |
||||
``` |
||||
|
||||
Jika hanya fix skrip agent tanpa perubahan collector, publish saja — tetapi **default asumsikan UI ikut deploy** bila field baru ditampilkan di UI. |
||||
|
||||
## 3. Dokumentasi & riwayat milestone (wajib) |
||||
|
||||
Sebelum nyatakan selesai, update: |
||||
|
||||
| File | Isi | |
||||
|------|-----| |
||||
| `.cursor/progress.md` | Baris **Riwayat milestone** + feature log versi + **Terakhir diupdate** | |
||||
| `perintah.md` | Versi agent saat ini + blok sync terakhir | |
||||
| `.cursor/api-contracts.md` | Field/kontrak baru (jika ada) | |
||||
| `agent/README.md` | Tabel versi | |
||||
| `.cursor/rules/project-context.mdc` | Baris **Agent saat ini** | |
||||
|
||||
Format milestone: `| YYYY-MM-DD | Agent vX.Y.Z — ringkasan fitur + deploy server |` |
||||
|
||||
## Checklist AI (jangan skip) |
||||
|
||||
- [ ] `agent/VERSION` = `install/manifest.json` version |
||||
- [ ] `publish_agent_install.ps1` sukses; manifest HTTPS cocok |
||||
- [ ] `push_collector.ps1` jika collector/UI berubah |
||||
- [ ] Riwayat milestone di `progress.md` ditambah |
||||
- [ ] Sync `perintah.md` sesuai protokol |
||||
|
||||
Server target: `ssh reverse-proxy` (10.100.1.24). Detail SSH: `.cursor/server.md`. |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
--- |
||||
description: Aturan dokumentasi — kapan harus update doc setelah perubahan |
||||
alwaysApply: true |
||||
--- |
||||
|
||||
# Documentation Rules |
||||
|
||||
## Setelah Perubahan Apapun |
||||
|
||||
| Jika berubah | Wajib update | |
||||
|--------------|-------------| |
||||
| Schema DB (migration baru) | `docs/database.md` + `.cursor/db-schema.md` | |
||||
| API endpoint baru/berubah | `docs/api/service-collector.md` + `.cursor/api-contracts.md` + `collector/docs/API.md` | |
||||
| Arsitektur berubah | `docs/architecture/system-overview.md` + ADR baru di `docs/adr/` | |
||||
| Keputusan teknologi baru | ADR baru di `docs/adr/ADR-00N-*.md` | |
||||
| Bug ditemukan/fix | `docs/issues.md` | |
||||
| Fase selesai (DoD) | `.cursor/progress.md` + `docs/project-status.md` | |
||||
| Deploy sukses | `perintah.md` (Sync terakhir) + `.cursor/progress.md` (Riwayat) | |
||||
| Variabel env baru | `docs/environment.md` + `.env.example` | |
||||
| Script server baru | `docs/server.md` | |
||||
|
||||
## Aturan ADR |
||||
|
||||
- ADR **tidak boleh dihapus** — buat ADR baru jika keputusan berubah |
||||
- Format file: `ADR-NNN-judul-kebab-case.md` di `docs/adr/` |
||||
- Update `docs/adr/README.md` setiap ada ADR baru |
||||
- Isi minimal: Konteks, Keputusan, Alternatif yang Ditolak, Konsekuensi |
||||
|
||||
## Definition of Done |
||||
|
||||
Implementasi belum selesai jika: |
||||
- [ ] Dokumentasi relevan belum diupdate |
||||
- [ ] `perintah.md` belum di-sync |
||||
- [ ] `.cursor/progress.md` belum mencatat milestone |
||||
- [ ] Secret baru belum didokumentasikan lokasinya di `docs/environment.md` (nilai tidak boleh di repo) |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
--- |
||||
description: Aturan keamanan — credential, token, SSH |
||||
alwaysApply: true |
||||
--- |
||||
|
||||
# Security Rules |
||||
|
||||
## Larangan Mutlak |
||||
|
||||
- ❌ Jangan commit `.env` ke repository |
||||
- ❌ Jangan commit password, API key, bearer token, private key ke repository |
||||
- ❌ Jangan tampilkan nilai secret di output percakapan/log |
||||
- ❌ Jangan hardcode credentials di kode Python atau PowerShell |
||||
|
||||
## Token & Credential |
||||
|
||||
| Secret | Lokasi yang Benar | |
||||
|--------|-------------------| |
||||
| DB credentials | `/root/geonetagent_dev.credentials` di server 10.100.1.25 | |
||||
| Agent bearer token | `.env` di server collector (10.100.1.24) | |
||||
| QNAP OSS credentials | `infra/qnap/README.md` (hanya referensi lokasi, nilai di server) | |
||||
| SSH keys | `~/.ssh/` di laptop developer (tidak di repo) | |
||||
|
||||
## SSH Command Rules |
||||
|
||||
- SSH command ke server wajib **minta persetujuan user** sebelum dijalankan |
||||
- Jangan auto-run SSH command yang bersifat destructive (rm, truncate, dll) |
||||
- Gunakan `scp -o "MACs=hmac-sha1"` untuk transfer file dari Windows |
||||
- Jangan heredoc `<< 'EOF'` via SSH dari PowerShell — `$` variabel terpotong |
||||
|
||||
## Nginx Security |
||||
|
||||
- `agent.gisportal.id/install/`: wajib ada `ssi off` — agar `$env:` di `.ps1` tidak terpotong |
||||
- Jangan expose port 18000 langsung ke internet |
||||
- SSL via certbot/Let's Encrypt — jangan matikan HTTPS |
||||
|
||||
## Aturan untuk AI Agent |
||||
|
||||
- Jika diminta membuat credential atau secret baru: catat **lokasi penyimpanan** di `docs/environment.md`, bukan nilainya |
||||
- Jika token bocor: instruksikan user untuk rotasi manual — jangan buat token baru di repo |
||||
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
# AGENTS.md — AI Agent Rules |
||||
|
||||
## Entry Point Wajib |
||||
|
||||
Sebelum melakukan pekerjaan apa pun, baca berurutan: |
||||
1. `docs/context-index.md` — peta seluruh dokumentasi |
||||
2. `.cursor/blueprint.md` — visi, fase, batas modul |
||||
3. `.cursor/progress.md` — fase aktif & checklist DoD |
||||
4. Dokumen spesifik modul (db-schema, api-contracts, dll) |
||||
|
||||
**Jangan implement fase N+1 jika fase N belum DoD.** |
||||
|
||||
--- |
||||
|
||||
## Aturan Kode |
||||
|
||||
### Python (FastAPI Collector) |
||||
- Gunakan `snake_case` untuk variable, function, file |
||||
- Type hints wajib di semua function signature |
||||
- Pydantic schema di `app/schemas/` — jangan inline model di routes |
||||
- Route di `app/api/` — satu file per resource |
||||
- DB session via dependency injection (`get_db`) |
||||
- Jangan commit `.env` — gunakan `.env.example` |
||||
|
||||
### PowerShell (Agent) |
||||
- Module di `agent/lib/` — satu file per sensor (Get-CpuInfo.ps1, dll) |
||||
- Jangan hardcode credential di skrip |
||||
- Hindari literal `$env:` di file `.ps1` yang di-serve Nginx (nginx potong respons) |
||||
- Test lokal dulu sebelum publish ke `agent.gisportal.id/install/` |
||||
|
||||
### SQL / Database |
||||
- PK: UUID (`gen_random_uuid()`) |
||||
- Timestamp: `timestamptz` |
||||
- Nama tabel: `snake_case` plural |
||||
- Setiap perubahan schema → update `docs/database.md` + `.cursor/db-schema.md` |
||||
- Migration order: lihat `infra/db/README.md` |
||||
|
||||
--- |
||||
|
||||
## Aturan Deploy |
||||
|
||||
**Wajib setelah bump versi agent:** |
||||
```powershell |
||||
.\infra\collector\publish_agent_install.ps1 # upload ke agent.gisportal.id/install/ |
||||
.\infra\collector\push_collector.ps1 # rebuild UI/API jika berubah |
||||
``` |
||||
|
||||
- Update `agent/VERSION` + `agent/install/manifest.json` sebelum publish |
||||
- Update `Riwayat milestone` di `.cursor/progress.md` |
||||
- Update `Sync terakhir` di `perintah.md` |
||||
|
||||
**File transfer ke server:** gunakan `scp -o "MACs=hmac-sha1"` dari Windows. |
||||
Jangan heredoc SSH dari PowerShell — `$` variabel terpotong. |
||||
|
||||
--- |
||||
|
||||
## Aturan Dokumentasi |
||||
|
||||
- Setiap perubahan arsitektur → buat ADR baru di `docs/adr/ADR-00N-*.md` |
||||
- ADR **tidak boleh dihapus** — buat ADR baru jika keputusan berubah |
||||
- Setelah implementasi: update `.cursor/progress.md` + `perintah.md` |
||||
- Implementasi tanpa dokumentasi = **belum selesai** |
||||
- Update `docs/context-index.md` jika ada dokumen baru |
||||
|
||||
--- |
||||
|
||||
## Aturan Keamanan |
||||
|
||||
- **Jangan commit secret**: password DB, bearer token, API key |
||||
- Credentials DB ada di `/root/geonetagent_dev.credentials` di server 10.100.1.25 |
||||
- Token agent ada di `.env` server — lihat `docs/environment.md` untuk nama variabelnya |
||||
- SSH command butuh approval user sebelum dijalankan |
||||
- Nginx `agent.gisportal.id`: `ssi off` — jangan taruh `$env:` literal di file `.ps1` yang di-serve |
||||
|
||||
--- |
||||
|
||||
## Batas Sistem (Non-goals) |
||||
|
||||
Jangan implement hal-hal berikut — sudah diputuskan di ADR/blueprint: |
||||
- ❌ Engine monitoring time-series (pakai Zabbix) |
||||
- ❌ NMS pengganti MikroTik/Proxmox tools |
||||
- ❌ Duplikasi data GIS legacy dari SQL Server |
||||
- ❌ Remote desktop / RMM agent |
||||
- ❌ Ticketing enterprise penuh (fase awal) |
||||
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
# Changelog — GeoNetAgent |
||||
|
||||
Format: [Semantic Versioning](https://semver.org) — Agent version / Doc version |
||||
|
||||
--- |
||||
|
||||
## [Doc 1.0.0] — 2026-06-29 |
||||
|
||||
### Added (Dokumentasi) |
||||
- `README.md` — root README lengkap dengan quick start + struktur monorepo |
||||
- `AGENTS.md` — aturan AI agent untuk proyek ini |
||||
- `CHANGELOG.md` — file ini |
||||
- `docs/context-index.md` ⭐ — AI entry point utama, peta navigasi semua doc |
||||
- `docs/project-status.md` — status modul, backlog, technical debt |
||||
- `docs/roadmap.md` — fase dan milestone |
||||
- `docs/todo.md` — next actions berprioritas |
||||
- `docs/issues.md` — known bugs + limitations |
||||
- `docs/environment.md` — env vars (tanpa secret) |
||||
- `docs/server.md` — server list + SSH commands |
||||
- `docs/deployment.md` — step-by-step deploy |
||||
- `docs/development.md` — local setup + coding guide |
||||
- `docs/database.md` — schema PostgreSQL + migration history |
||||
- `docs/troubleshooting.md` — common issues + fixes |
||||
- `docs/monitoring.md` — Zabbix boundary + health check |
||||
- `docs/security.md` — credential management + rules |
||||
- `docs/glossary.md` — kamus istilah |
||||
- `docs/architecture/system-overview.md` — diagram sistem + boundary |
||||
- `docs/architecture/folder-structure.md` — struktur monorepo detail |
||||
- `docs/architecture/request-flow.md` — sequence diagram alur agent |
||||
- `docs/architecture/authentication-flow.md` — auth bearer token + Laravel (Fase 1) |
||||
- `docs/architecture/deployment-architecture.md` — topology + Nginx config |
||||
- `docs/architecture/database-flow.md` — write/read ownership + view |
||||
- `docs/api/overview.md` — base URL, auth, format response |
||||
- `docs/api/service-collector.md` — endpoint FastAPI lengkap + payload schema |
||||
- `docs/api/error-codes.md` — HTTP status codes |
||||
- `docs/adr/README.md` — index ADR |
||||
- `docs/adr/ADR-001-agent-asset-identity.md` — strategi identitas asset |
||||
- `docs/adr/ADR-002-asset-split-by-hardware.md` — split asset hardware campur |
||||
- `docs/adr/ADR-003-asset-documents-qnap.md` — dokumen di QNAP Object Storage |
||||
- `docs/diagrams/system.mmd` — Mermaid diagram sistem |
||||
- `docs/diagrams/agent-report-flow.mmd` — Mermaid sequence laporan agent |
||||
- `docs/diagrams/deployment.mmd` — Mermaid deployment topology |
||||
- `docs/onboarding/first-day.md` — panduan hari pertama |
||||
- `docs/onboarding/local-setup.md` — setup lokal developer |
||||
- `docs/onboarding/coding-standard.md` — standar kode |
||||
- `docs/decisions/technology-stack.md` — keputusan stack teknologi |
||||
- `docs/decisions/naming-convention.md` — keputusan naming convention |
||||
- `.cursor/rules/documentation.mdc` — aturan kapan harus update doc |
||||
- `.cursor/rules/security.mdc` — aturan keamanan + credential |
||||
|
||||
--- |
||||
|
||||
## [Agent v0.1.10-phase0] — 2026-06-22 |
||||
|
||||
### Added |
||||
- `cpu{}` detail object: cores, clock, cache, arch |
||||
- Deploy script `push_collector.ps1` PowerShell |
||||
- Auto-update manifest check sebelum collect |
||||
|
||||
### Changed |
||||
- Field CPU sebelumnya hanya ada di `system.cpu_model` — sekarang ada object terpisah `cpu{}` |
||||
|
||||
--- |
||||
|
||||
## [Agent v0.1.9] — 2026-06-22 |
||||
|
||||
### Added |
||||
- `physical_disks[]` — disk fisik (model, serial, size, media_type, bus_type) |
||||
- RAM DDR type label (DDR3/DDR4/DDR5) |
||||
- GPU detail: driver version, current resolution, pnp_device_id |
||||
- `form_factor`: Laptop/Desktop/Server dari SMBIOS + PCSystemType |
||||
|
||||
--- |
||||
|
||||
## [Agent v0.1.8] — 2026-06-22 |
||||
|
||||
### Fixed |
||||
- **Nginx `$env:` cut-off bug** — tambah `ssi off` di nginx config |
||||
|
||||
### Changed |
||||
- Bump versi untuk publish ke server |
||||
|
||||
--- |
||||
|
||||
## [Agent v0.1.6] — 2026-06-17 |
||||
|
||||
### Added |
||||
- Peripherals scan: USB/BT/audio/monitor (`is_external` flag) |
||||
- Scheduled Task setiap 3 jam (SYSTEM account) |
||||
- Web install `agent.gisportal.id/install/` |
||||
|
||||
--- |
||||
|
||||
## [Collector v0.1.0] — 2026-06-17 |
||||
|
||||
### Added |
||||
- FastAPI: `POST /api/v1/agent/report` + upsert assets/snapshots/software |
||||
- FastAPI: `GET /api/v1/assets` + detail + snapshots + software |
||||
- FastAPI: `GET /health` |
||||
- UI: `/ui/` read-only CMDB |
||||
- PostgreSQL migrations 001-004 |
||||
- Nginx reverse proxy: `agent.gisportal.id` + `collector.gisportal.id` |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
# GeoNetAgent — IT Asset Monitoring & CMDB |
||||
|
||||
Portal IT + CMDB ringan yang melengkapi Zabbix. Mengumpulkan data hardware/software dari endpoint Windows via PowerShell agent, menyimpannya ke PostgreSQL melalui FastAPI collector, dan menampilkannya di portal Laravel. |
||||
|
||||
> **AI Agent & Developer baru:** Mulai dari [`docs/context-index.md`](docs/context-index.md) — peta navigasi seluruh dokumentasi. |
||||
|
||||
--- |
||||
|
||||
## Komponen |
||||
|
||||
| Komponen | Stack | Lokasi | URL Produksi | |
||||
|----------|-------|--------|--------------| |
||||
| **PowerShell Agent** | PS 5.1+ | `agent/` | Install: `agent.gisportal.id/install/` | |
||||
| **FastAPI Collector** | Python 3.11 + FastAPI | `collector/` | `collector.gisportal.id` | |
||||
| **Infrastruktur** | Nginx, Docker, DB migrations | `infra/` | - | |
||||
| **Laravel Portal** | Laravel (belum scaffold) | `portal/` (planned) | `portal.geonet.internal` | |
||||
|
||||
--- |
||||
|
||||
## Quick Start — Install Agent (User) |
||||
|
||||
Jalankan PowerShell sebagai **Administrator**, dua baris berurutan: |
||||
|
||||
```powershell |
||||
Invoke-WebRequest -Uri 'https://agent.gisportal.id/install/GeoNetAgent-Install.ps1' -OutFile "$env:TEMP\GeoNetAgent-Install.ps1" -UseBasicParsing |
||||
& "$env:TEMP\GeoNetAgent-Install.ps1" |
||||
``` |
||||
|
||||
Untuk update: perintah yang sama. `config.json` lokal dipertahankan. |
||||
|
||||
--- |
||||
|
||||
## Quick Start — Developer |
||||
|
||||
```bash |
||||
cd collector |
||||
cp ../.env.example .env |
||||
# Edit .env — isi DATABASE_URL dan AGENT_BEARER_TOKEN |
||||
pip install -e ".[dev]" |
||||
uvicorn app.main:app --reload --port 8000 |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Deploy (Admin IT) |
||||
|
||||
```powershell |
||||
# Publish agent ke server (setelah bump versi) |
||||
.\infra\collector\publish_agent_install.ps1 |
||||
|
||||
# Deploy collector (FastAPI) |
||||
.\infra\collector\push_collector.ps1 |
||||
|
||||
# Deploy nginx config |
||||
bash infra/collector/deploy_nginx.sh |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Status Proyek |
||||
|
||||
| Fase | Status | Keterangan | |
||||
|------|--------|------------| |
||||
| Fase 0 — Blueprint | ✅ Selesai | Dokumentasi + scaffold | |
||||
| Fase 1 — Foundation | 🔄 Aktif (8/9) | Agent E2E → DB → UI read-only · Blocker: Laravel asset index | |
||||
| Fase 2 — Zabbix | ⏳ Pending | Webhook alert + host status | |
||||
| Fase 3 — CMDB Lengkap | ⏳ Pending | Proxmox sync, license, dokumen QNAP | |
||||
| Fase 4 — Scale | ⏳ Pending | GPO, trend, Ollama | |
||||
|
||||
Agent versi saat ini: **v0.1.10-phase0** |
||||
|
||||
--- |
||||
|
||||
## Struktur Monorepo |
||||
|
||||
``` |
||||
GeoNetAgent/ |
||||
├── agent/ — PowerShell agent (install ke endpoint user) |
||||
│ ├── lib/ — Modul collect: CPU, disk, RAM, GPU, network, software, peripherals |
||||
│ ├── gpo/ — Script GPO untuk rollout massal |
||||
│ └── install/ — Entry point web install |
||||
├── collector/ — FastAPI service (terima laporan agent, read API) |
||||
│ ├── app/ — Source FastAPI |
||||
│ └── docs/API.md — API documentation |
||||
├── infra/ — Nginx, DB migrations, Docker, QNAP config |
||||
│ ├── collector/ — Deploy scripts |
||||
│ ├── db/ — SQL migrations |
||||
│ ├── nginx/ — Nginx config |
||||
│ └── qnap/ — QNAP Object Storage config |
||||
├── samples/ — Sample payload & DXDiag untuk testing |
||||
├── docs/ — Dokumentasi lengkap ⭐ |
||||
└── .cursor/ — AI context (blueprint, schema, API, progress) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Dokumentasi Lengkap |
||||
|
||||
Lihat [`docs/context-index.md`](docs/context-index.md) untuk peta dokumentasi. |
||||
|
||||
| Topik | File | |
||||
|-------|------| |
||||
| Status & progress | `docs/project-status.md` | |
||||
| Arsitektur sistem | `docs/architecture/system-overview.md` | |
||||
| API Collector | `docs/api/service-collector.md` | |
||||
| Database schema | `docs/database.md` | |
||||
| Deploy guide | `docs/deployment.md` | |
||||
| Environment vars | `docs/environment.md` | |
||||
| Troubleshooting | `docs/troubleshooting.md` | |
||||
| ADR | `docs/adr/` | |
||||
|
||||
--- |
||||
|
||||
## ADR (Architecture Decision Records) |
||||
|
||||
| ADR | Keputusan | |
||||
|-----|-----------| |
||||
| [ADR-001](docs/adr/ADR-001-agent-asset-identity.md) | Strategi identitas asset (serial → smbios_uuid → MAC) | |
||||
| [ADR-002](docs/adr/ADR-002-asset-split-by-hardware.md) | Split asset dengan hardware campur | |
||||
| [ADR-003](docs/adr/ADR-003-asset-documents-qnap.md) | Dokumen procurement di QNAP Object Storage | |
||||
@ -0,0 +1,207 @@
@@ -0,0 +1,207 @@
|
||||
function Build-GeoNetReportPayload { |
||||
param( |
||||
[Parameter(Mandatory)] |
||||
[string]$AgentVersion, |
||||
|
||||
[Parameter(Mandatory)] |
||||
[string]$SchemaVersion, |
||||
|
||||
[Parameter(Mandatory)] |
||||
[string]$CollectMode, |
||||
|
||||
[switch]$IncludeSoftware, |
||||
[int]$MaxSoftware = 100 |
||||
) |
||||
|
||||
$sys = Get-GeoNetSystemInfo |
||||
$disks = @(Invoke-GeoNetSafe { @(Get-GeoNetDiskInfo) } -Default @()) |
||||
$physicalDisks = @(Invoke-GeoNetSafe { @(Get-GeoNetPhysicalDiskInfo) } -Default @()) |
||||
$memory = Invoke-GeoNetSafe { Get-GeoNetMemoryInfo } -Default @{ Modules = @(); Summary = $null } |
||||
$network = @(Invoke-GeoNetSafe { @(Get-GeoNetNetworkInfo) } -Default @()) |
||||
$identity = Get-GeoNetAssetIdentity ` |
||||
-SerialNumber (Convert-GeoNetOptionalString -Value $sys.SerialNumber) ` |
||||
-SmbiosUuid (Convert-GeoNetOptionalString -Value $sys.SmbiosUuid) ` |
||||
-MachineId (Convert-GeoNetOptionalString -Value $sys.MachineId) ` |
||||
-Hostname (Convert-GeoNetOptionalString -Value $sys.Hostname) ` |
||||
-Network $network |
||||
$health = Invoke-GeoNetSafe { Get-GeoNetHealthInfo } -Default @{ |
||||
bsod_count_30d = 0 |
||||
pending_reboot = $false |
||||
last_patch_installed = $null |
||||
} |
||||
$peripherals = @(Invoke-GeoNetSafe { @(Get-GeoNetPeripheralsInfo) } -Default @()) |
||||
$software = @() |
||||
if ($IncludeSoftware) { |
||||
$software = @(Get-GeoNetSoftwareInfo -MaxItems $MaxSoftware) |
||||
} |
||||
|
||||
$collectedAt = Format-GeoNetIsoDateTime -Value (Get-Date) |
||||
|
||||
[ordered]@{ |
||||
schema_version = $SchemaVersion |
||||
agent_version = $AgentVersion |
||||
collected_at = $collectedAt |
||||
hostname = (Convert-GeoNetOptionalString -Value $sys.Hostname) |
||||
machine_id = (Convert-GeoNetOptionalString -Value $sys.MachineId) |
||||
smbios_uuid = (Convert-GeoNetOptionalString -Value $sys.SmbiosUuid) |
||||
serial_number = (Convert-GeoNetOptionalString -Value $sys.SerialNumber) |
||||
asset_type = 'endpoint' |
||||
logged_in_user = (Convert-GeoNetOptionalString -Value $sys.LoggedInUser) |
||||
system = [ordered]@{ |
||||
manufacturer = (Convert-GeoNetOptionalString -Value $sys.Manufacturer) |
||||
model = (Convert-GeoNetOptionalString -Value $sys.Model) |
||||
bios_version = (Convert-GeoNetOptionalString -Value $sys.BiosVersion) |
||||
os_name = (Convert-GeoNetOptionalString -Value $sys.OsName) |
||||
os_version = (Convert-GeoNetOptionalString -Value $sys.OsVersion) |
||||
os_build = (Convert-GeoNetOptionalString -Value $sys.OsBuild) |
||||
cpu_model = (Convert-GeoNetOptionalString -Value $sys.CpuModel) |
||||
ram_gb = $sys.RamGb |
||||
gpu_model = (Convert-GeoNetOptionalString -Value $sys.GpuModel) |
||||
last_boot_at = $sys.LastBootAt |
||||
form_factor = (Convert-GeoNetOptionalString -Value $sys.FormFactor) |
||||
chassis_label = (Convert-GeoNetOptionalString -Value $sys.ChassisLabel) |
||||
chassis_types = @($sys.ChassisTypes) |
||||
pc_system_type = $sys.PcSystemType |
||||
pc_system_label = (Convert-GeoNetOptionalString -Value $sys.PcSystemLabel) |
||||
} |
||||
cpu = if ($sys.Cpu) { |
||||
[ordered]@{ |
||||
model = $sys.Cpu.model |
||||
manufacturer = $sys.Cpu.manufacturer |
||||
cores = $sys.Cpu.cores |
||||
logical_processors = $sys.Cpu.logical_processors |
||||
max_clock_mhz = $sys.Cpu.max_clock_mhz |
||||
current_clock_mhz = $sys.Cpu.current_clock_mhz |
||||
socket = $sys.Cpu.socket |
||||
socket_count = $sys.Cpu.socket_count |
||||
l2_cache_kb = $sys.Cpu.l2_cache_kb |
||||
l3_cache_kb = $sys.Cpu.l3_cache_kb |
||||
architecture = $sys.Cpu.architecture |
||||
} |
||||
} else { |
||||
$null |
||||
} |
||||
gpus = @($sys.Gpus | ForEach-Object { |
||||
[ordered]@{ |
||||
name = $_.name |
||||
adapter_compatibility = $_.adapter_compatibility |
||||
video_processor = $_.video_processor |
||||
driver_version = $_.driver_version |
||||
driver_date = $_.driver_date |
||||
dedicated_memory_mb = $_.dedicated_memory_mb |
||||
pnp_device_id = $_.pnp_device_id |
||||
status = $_.status |
||||
current_resolution = $_.current_resolution |
||||
video_mode_description = $_.video_mode_description |
||||
} |
||||
}) |
||||
disks = @($disks | ForEach-Object { |
||||
[ordered]@{ |
||||
drive_letter = $_.drive_letter |
||||
total_gb = $_.total_gb |
||||
free_gb = $_.free_gb |
||||
pct_used = $_.pct_used |
||||
file_system = $_.file_system |
||||
model = $_.model |
||||
} |
||||
}) |
||||
physical_disks = @($physicalDisks | ForEach-Object { |
||||
[ordered]@{ |
||||
disk_number = $_.disk_number |
||||
device_id = $_.device_id |
||||
model = $_.model |
||||
friendly_name = $_.friendly_name |
||||
media_type = $_.media_type |
||||
operational_status = $_.operational_status |
||||
health_status = $_.health_status |
||||
total_gb = $_.total_gb |
||||
partition_style = $_.partition_style |
||||
bus_type = $_.bus_type |
||||
serial_number = $_.serial_number |
||||
} |
||||
}) |
||||
memory_modules = @($memory.Modules | ForEach-Object { |
||||
[ordered]@{ |
||||
slot = $_.slot |
||||
bank_label = $_.bank_label |
||||
capacity_gb = $_.capacity_gb |
||||
speed_mhz = $_.speed_mhz |
||||
configured_speed_mhz = $_.configured_speed_mhz |
||||
manufacturer = $_.manufacturer |
||||
part_number = $_.part_number |
||||
serial_number = $_.serial_number |
||||
form_factor = $_.form_factor |
||||
memory_type = $_.memory_type |
||||
memory_type_label = $_.memory_type_label |
||||
data_width = $_.data_width |
||||
total_width = $_.total_width |
||||
rank = $_.rank |
||||
configured_voltage_mv = $_.configured_voltage_mv |
||||
} |
||||
}) |
||||
memory_summary = if ($memory.Summary) { |
||||
[ordered]@{ |
||||
total_gb = $memory.Summary.total_gb |
||||
module_count = $memory.Summary.module_count |
||||
slot_count = $memory.Summary.slot_count |
||||
empty_slots = $memory.Summary.empty_slots |
||||
layout = $memory.Summary.layout |
||||
configuration = $memory.Summary.configuration |
||||
upgradeable = $memory.Summary.upgradeable |
||||
ddr_generation = $memory.Summary.ddr_generation |
||||
} |
||||
} else { |
||||
$null |
||||
} |
||||
network = @($network | ForEach-Object { |
||||
[ordered]@{ |
||||
adapter_name = $_.adapter_name |
||||
mac_address = $_.mac_address |
||||
ip_address = $_.ip_address |
||||
adapter_kind = $_.adapter_kind |
||||
status = $_.status |
||||
is_primary = $_.is_primary |
||||
is_built_in = $_.is_built_in |
||||
} |
||||
}) |
||||
asset_identity = [ordered]@{ |
||||
primary_physical_mac = (Convert-GeoNetOptionalString -Value $identity.PrimaryPhysicalMac) |
||||
primary_physical_adapter = (Convert-GeoNetOptionalString -Value $identity.PrimaryPhysicalAdapter) |
||||
primary_mac_built_in = $identity.PrimaryMacBuiltIn |
||||
primary_mac_source = (Convert-GeoNetOptionalString -Value $identity.PrimaryMacSource) |
||||
merge_key = (Convert-GeoNetOptionalString -Value $identity.MergeKey) |
||||
recommended_match_order = $identity.RecommendedMatchOrder |
||||
} |
||||
peripherals = @($peripherals | ForEach-Object { |
||||
[ordered]@{ |
||||
name = $_.name |
||||
device_class = $_.device_class |
||||
manufacturer = $_.manufacturer |
||||
connection = $_.connection |
||||
device_role = $_.device_role |
||||
vid = $_.vid |
||||
pid = $_.pid |
||||
pnp_device_id = $_.pnp_device_id |
||||
device_serial = $_.device_serial |
||||
edid_product = $_.edid_product |
||||
resolution = $_.resolution |
||||
resolution_width = $_.resolution_width |
||||
resolution_height = $_.resolution_height |
||||
refresh_hz = $_.refresh_hz |
||||
status = $_.status |
||||
is_external = $_.is_external |
||||
} |
||||
}) |
||||
health = [ordered]@{ |
||||
bsod_count_30d = $health.bsod_count_30d |
||||
pending_reboot = $health.pending_reboot |
||||
last_patch_installed = $health.last_patch_installed |
||||
} |
||||
software = $software |
||||
metadata = [ordered]@{ |
||||
collect_mode = $CollectMode |
||||
software_included = [bool]$IncludeSoftware |
||||
software_count = $software.Count |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
$formatText = Join-Path $PSScriptRoot 'Format-Text.ps1' |
||||
if (Test-Path $formatText) { . $formatText } |
||||
|
||||
function Get-GeoNetCpuArchitectureName { |
||||
param([uint16]$Code) |
||||
|
||||
switch ($Code) { |
||||
0 { return 'x86' } |
||||
1 { return 'MIPS' } |
||||
2 { return 'Alpha' } |
||||
3 { return 'PowerPC' } |
||||
5 { return 'ARM' } |
||||
6 { return 'ia64' } |
||||
9 { return 'x64' } |
||||
12 { return 'ARM64' } |
||||
default { return "arch_$Code" } |
||||
} |
||||
} |
||||
|
||||
function Get-GeoNetCpuInfo { |
||||
$processors = @(Invoke-GeoNetSafe { |
||||
Get-CimInstance Win32_Processor -ErrorAction Stop |
||||
} -Default @()) |
||||
|
||||
if (-not $processors.Count) { return $null } |
||||
|
||||
$primary = $processors[0] |
||||
$cores = [int]($processors | Measure-Object -Property NumberOfCores -Sum).Sum |
||||
$logical = [int]($processors | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum |
||||
|
||||
$maxClock = ($processors | Where-Object { $_.MaxClockSpeed -gt 0 } | Select-Object -ExpandProperty MaxClockSpeed | Measure-Object -Maximum).Maximum |
||||
$currentClock = ($processors | Where-Object { $_.CurrentClockSpeed -gt 0 } | Select-Object -ExpandProperty CurrentClockSpeed | Measure-Object -Maximum).Maximum |
||||
|
||||
$l2 = ($processors | Where-Object { $_.L2CacheSize -gt 0 } | Select-Object -ExpandProperty L2CacheSize | Measure-Object -Sum).Sum |
||||
$l3 = ($processors | Where-Object { $_.L3CacheSize -gt 0 } | Select-Object -ExpandProperty L3CacheSize | Measure-Object -Sum).Sum |
||||
|
||||
$sockets = @($processors | ForEach-Object { |
||||
Convert-GeoNetOptionalString -Value $_.SocketDesignation |
||||
} | Where-Object { $_ } | Select-Object -Unique) |
||||
|
||||
$socketLabel = if ($sockets.Count -eq 1) { $sockets[0] } |
||||
elseif ($sockets.Count -gt 1) { ($sockets -join ', ') } |
||||
else { $null } |
||||
|
||||
$archCode = if ($null -ne $primary.Architecture) { [uint16]$primary.Architecture } else { 0 } |
||||
|
||||
[pscustomobject]@{ |
||||
model = Convert-GeoNetOptionalString -Value $primary.Name |
||||
manufacturer = Convert-GeoNetOptionalString -Value $primary.Manufacturer |
||||
cores = if ($cores -gt 0) { $cores } else { $null } |
||||
logical_processors = if ($logical -gt 0) { $logical } else { $null } |
||||
max_clock_mhz = if ($maxClock -gt 0) { [int]$maxClock } else { $null } |
||||
current_clock_mhz = if ($currentClock -gt 0) { [int]$currentClock } else { $null } |
||||
socket = $socketLabel |
||||
socket_count = $processors.Count |
||||
l2_cache_kb = if ($l2 -gt 0) { [int]$l2 } else { $null } |
||||
l3_cache_kb = if ($l3 -gt 0) { [int]$l3 } else { $null } |
||||
architecture = Get-GeoNetCpuArchitectureName -Code $archCode |
||||
} |
||||
} |
||||
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
$formatText = Join-Path $PSScriptRoot 'Format-Text.ps1' |
||||
if (Test-Path $formatText) { . $formatText } |
||||
|
||||
function Convert-GeoNetMediaTypeName { |
||||
param([object]$Value) |
||||
|
||||
if ($null -eq $Value) { return $null } |
||||
$text = $Value.ToString() |
||||
switch ($text) { |
||||
'SSD' { return 'SSD' } |
||||
'HDD' { return 'HDD' } |
||||
'SCM' { return 'SCM' } |
||||
'Unspecified' { return $null } |
||||
default { |
||||
if ($Value -is [int] -or $Value -is [uint32]) { |
||||
switch ([int]$Value) { |
||||
3 { return 'HDD' } |
||||
4 { return 'SSD' } |
||||
5 { return 'SCM' } |
||||
} |
||||
} |
||||
return $text |
||||
} |
||||
} |
||||
} |
||||
|
||||
function Convert-GeoNetBusTypeName { |
||||
param([object]$Value) |
||||
|
||||
if ($null -eq $Value) { return $null } |
||||
$text = $Value.ToString() |
||||
switch ($text) { |
||||
'NVMe' { return 'NVMe' } |
||||
'SATA' { return 'SATA' } |
||||
'SAS' { return 'SAS' } |
||||
'USB' { return 'USB' } |
||||
'RAID' { return 'RAID' } |
||||
default { |
||||
if ($Value -is [int] -or $Value -is [uint32]) { |
||||
switch ([int]$Value) { |
||||
7 { return 'USB' } |
||||
11 { return 'NVMe' } |
||||
17 { return 'NVMe' } |
||||
3 { return 'SATA' } |
||||
} |
||||
} |
||||
return $text |
||||
} |
||||
} |
||||
} |
||||
|
||||
function Convert-GeoNetPartitionStyleName { |
||||
param([object]$Value) |
||||
|
||||
if ($null -eq $Value) { return $null } |
||||
$text = $Value.ToString() |
||||
switch ($text) { |
||||
'MBR' { return 'MBR' } |
||||
'GPT' { return 'GPT' } |
||||
default { |
||||
if ($Value -is [int] -or $Value -is [uint32]) { |
||||
switch ([int]$Value) { |
||||
1 { return 'MBR' } |
||||
2 { return 'GPT' } |
||||
} |
||||
} |
||||
return $text |
||||
} |
||||
} |
||||
} |
||||
|
||||
function Get-GeoNetPhysicalDiskInfo { |
||||
Invoke-GeoNetSafe { |
||||
$physical = @(Get-PhysicalDisk -ErrorAction Stop) |
||||
$logical = @(Get-Disk -ErrorAction Stop) |
||||
$rows = @() |
||||
|
||||
foreach ($pd in $physical) { |
||||
$disk = $logical | Where-Object { $_.Number -eq $pd.DeviceId } | Select-Object -First 1 |
||||
$totalGb = if ($pd.Size -gt 0) { [math]::Round($pd.Size / 1GB, 1) } else { $null } |
||||
|
||||
$rows += [pscustomobject]@{ |
||||
disk_number = [int]$pd.DeviceId |
||||
device_id = [string]$pd.DeviceId |
||||
model = Convert-GeoNetOptionalString -Value $pd.Model |
||||
friendly_name = Convert-GeoNetOptionalString -Value $(if ($disk) { $disk.FriendlyName } else { $pd.FriendlyName }) |
||||
media_type = Convert-GeoNetMediaTypeName -Value $pd.MediaType |
||||
operational_status = Convert-GeoNetOptionalString -Value $pd.OperationalStatus |
||||
health_status = Convert-GeoNetOptionalString -Value $pd.HealthStatus |
||||
total_gb = $totalGb |
||||
partition_style = Convert-GeoNetPartitionStyleName -Value $(if ($disk) { $disk.PartitionStyle } else { $null }) |
||||
bus_type = Convert-GeoNetBusTypeName -Value $pd.BusType |
||||
serial_number = Convert-GeoNetOptionalString -Value $pd.SerialNumber |
||||
unique_id = Convert-GeoNetOptionalString -Value $pd.UniqueId |
||||
} |
||||
} |
||||
|
||||
@($rows | Sort-Object disk_number) |
||||
} -Default @() |
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
import sys |
||||
import uuid |
||||
|
||||
from app.db.session import SessionLocal |
||||
from app.services.asset_split_service import split_asset_by_hardware |
||||
|
||||
asset_id = uuid.UUID(sys.argv[1]) |
||||
dry_run = "--dry-run" in sys.argv |
||||
|
||||
db = SessionLocal() |
||||
try: |
||||
result = split_asset_by_hardware(db, asset_id, dry_run=dry_run) |
||||
finally: |
||||
db.close() |
||||
|
||||
mode = "DRY-RUN" if result.dry_run else "APPLIED" |
||||
print(f"[{mode}] source asset {result.source_asset_id}") |
||||
print(f" primary: {result.primary_fingerprint}") |
||||
for group in result.groups: |
||||
marker = " *" if group.fingerprint == result.primary_fingerprint else "" |
||||
print(f" - {group.fingerprint}{marker}: {len(group.snapshot_ids)} · {group.label}") |
||||
for created in result.created_assets: |
||||
print(f" + {created['hostname']} ({created['display_name']}) ← {created['snapshot_count']}") |
||||
print(f" moved: {result.moved_snapshots}") |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
"""CLI: split merged asset snapshots by hardware profile.""" |
||||
|
||||
from __future__ import annotations |
||||
|
||||
import argparse |
||||
import sys |
||||
import uuid |
||||
|
||||
from app.db.session import SessionLocal |
||||
from app.services.asset_split_service import split_asset_by_hardware |
||||
|
||||
|
||||
def main() -> int: |
||||
parser = argparse.ArgumentParser(description="Split asset snapshots by hardware profile (SMBIOS / model).") |
||||
parser.add_argument("asset_id", help="Asset UUID to split") |
||||
parser.add_argument("--dry-run", action="store_true", help="Show plan only; do not write DB") |
||||
args = parser.parse_args() |
||||
|
||||
try: |
||||
asset_uuid = uuid.UUID(args.asset_id) |
||||
except ValueError: |
||||
print(f"Invalid asset_id: {args.asset_id}", file=sys.stderr) |
||||
return 1 |
||||
|
||||
db = SessionLocal() |
||||
try: |
||||
result = split_asset_by_hardware(db, asset_uuid, dry_run=args.dry_run) |
||||
except ValueError as exc: |
||||
print(f"ERROR: {exc}", file=sys.stderr) |
||||
return 1 |
||||
finally: |
||||
db.close() |
||||
|
||||
mode = "DRY-RUN" if result.dry_run else "APPLIED" |
||||
print(f"[{mode}] source asset {result.source_asset_id}") |
||||
print(f" hardware groups: {len(result.groups)} (primary: {result.primary_fingerprint})") |
||||
for group in result.groups: |
||||
marker = " *" if group.fingerprint == result.primary_fingerprint else "" |
||||
print(f" - {group.fingerprint}{marker}: {len(group.snapshot_ids)} snapshots · {group.label}") |
||||
for created in result.created_assets: |
||||
print( |
||||
f" + new asset {created['hostname']} ({created['display_name']}) " |
||||
f"← {created['snapshot_count']} snapshots" |
||||
) |
||||
print(f" moved snapshots: {result.moved_snapshots}") |
||||
return 0 |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
raise SystemExit(main()) |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
from unittest.mock import MagicMock |
||||
|
||||
from app.schemas.agent_report import AgentReportRequest, HealthInfo, SystemInfo |
||||
from app.services.asset_identity import ( |
||||
allocate_hostname, |
||||
hardware_identity_compatible, |
||||
normalize_smbios_uuid, |
||||
) |
||||
|
||||
|
||||
def _report(**overrides): |
||||
base = { |
||||
"schema_version": "1.0", |
||||
"agent_version": "0.1.8-phase0", |
||||
"collected_at": "2026-06-22T12:00:00+07:00", |
||||
"hostname": "DESKTOP-TEST", |
||||
"system": SystemInfo(), |
||||
"health": HealthInfo(), |
||||
} |
||||
base.update(overrides) |
||||
return AgentReportRequest(**base) |
||||
|
||||
|
||||
def test_normalize_smbios_uuid_strips_braces_and_uppercases(): |
||||
assert normalize_smbios_uuid("{03d502e0-045e-0550-8e06-ba0700080009}") == "03D502E0-045E-0550-8E06-BA0700080009" |
||||
|
||||
|
||||
def test_hardware_compatible_when_smbios_matches(): |
||||
asset = MagicMock(smbios_uuid="AAAA-BBBB", serial_number=None) |
||||
report = _report(smbios_uuid="{aaaa-bbbb}") |
||||
assert hardware_identity_compatible(asset, report) is True |
||||
|
||||
|
||||
def test_hardware_incompatible_when_smbios_differs(): |
||||
asset = MagicMock( |
||||
smbios_uuid="4C4C4544-004C-5610-805A-B2C04F4A3432", |
||||
serial_number=None, |
||||
) |
||||
report = _report(smbios_uuid="03D502E0-045E-0550-8E06-BA0700080009") |
||||
assert hardware_identity_compatible(asset, report) is False |
||||
|
||||
|
||||
def test_hardware_compatible_when_asset_has_no_smbios_yet(): |
||||
asset = MagicMock(smbios_uuid=None, serial_number=None) |
||||
report = _report(smbios_uuid="03D502E0-045E-0550-8E06-BA0700080009") |
||||
assert hardware_identity_compatible(asset, report) is True |
||||
|
||||
|
||||
def test_allocate_hostname_uses_smbios_suffix_on_collision(): |
||||
db = MagicMock() |
||||
db.scalar.side_effect = [object(), None] |
||||
|
||||
hostname, display_name = allocate_hostname( |
||||
db, |
||||
_report( |
||||
hostname="DESKTOP-TEST", |
||||
smbios_uuid="03D502E0-045E-0550-8E06-BA0700080009", |
||||
), |
||||
) |
||||
assert hostname == "DESKTOP-TEST-03D502E0" |
||||
assert display_name == "DESKTOP-TEST" |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
from app.services.asset_split_service import hardware_fingerprint, hardware_label |
||||
|
||||
|
||||
def test_hardware_fingerprint_prefers_smbios(): |
||||
payload = { |
||||
"smbios_uuid": "{4c4c4544-004c-5610-805a-b2c04f4a3432}", |
||||
"system": {"manufacturer": "Dell Inc.", "model": "Inspiron 7447"}, |
||||
} |
||||
assert hardware_fingerprint(payload) == "smbios:4C4C4544-004C-5610-805A-B2C04F4A3432" |
||||
|
||||
|
||||
def test_hardware_fingerprint_falls_back_to_product(): |
||||
payload = {"system": {"manufacturer": "Dell Inc.", "model": "Inspiron 7447"}} |
||||
assert hardware_fingerprint(payload) == "product:Dell Inc.|Inspiron 7447" |
||||
|
||||
|
||||
def test_hardware_label(): |
||||
payload = {"system": {"manufacturer": "Dell Inc.", "model": "Inspiron 7447"}} |
||||
assert hardware_label(payload) == "Dell Inc. Inspiron 7447" |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
# ADR-001 — Strategi Identitas Asset |
||||
|
||||
**Status:** Accepted |
||||
**Tanggal:** 2026-06-17 |
||||
**Penulis:** Tim IT GIS Portal |
||||
|
||||
--- |
||||
|
||||
## Konteks |
||||
|
||||
Agent PowerShell berjalan di berbagai endpoint Windows. Kita perlu menentukan cara mengidentifikasi aset secara unik agar: |
||||
- Laporan dari mesin yang sama selalu di-merge ke satu asset record |
||||
- Tidak membuat duplikat jika hostname berubah atau OS diinstall ulang |
||||
- Menangani kasus edge: komputer tanpa serial number, VM clone, dual-boot |
||||
|
||||
--- |
||||
|
||||
## Keputusan |
||||
|
||||
Gunakan **hierarki identitas fallback** untuk resolve `asset_id`: |
||||
|
||||
``` |
||||
1. serial_number (paling stabil, hardware-bound) |
||||
2. smbios_uuid (BIOS UUID, umumnya stabil) |
||||
3. primary_physical_mac (MAC NIC built-in utama, stabil kecuali ganti NIC) |
||||
4. machine_id (Registry MachineGuid, berubah jika clean install) |
||||
5. hostname (last resort — rentan berubah) |
||||
``` |
||||
|
||||
**Primary physical MAC** dipilih berdasarkan: |
||||
- NIC dengan route ke gateway default |
||||
- Filter: hanya physical (bukan virtual/VPN) |
||||
- Prefer `is_built_in: true` |
||||
|
||||
Algoritma ada di `agent/lib/Get-AssetIdentity.ps1` + `agent/lib/Build-GeoNetReportPayload.ps1`. |
||||
|
||||
--- |
||||
|
||||
## Implementasi |
||||
|
||||
Field `asset_identity` di payload: |
||||
```json |
||||
{ |
||||
"primary_physical_mac": "B8:2A:72:FA:17:FF", |
||||
"merge_key": "mac:B8:2A:72:FA:17:FF", |
||||
"recommended_match_order": ["serial_number", "smbios_uuid", "primary_physical_mac", "machine_id", "hostname"] |
||||
} |
||||
``` |
||||
|
||||
FastAPI Collector menggunakan `merge_key` untuk UPSERT ke tabel `assets`. |
||||
|
||||
--- |
||||
|
||||
## Alternatif yang Ditolak |
||||
|
||||
| Alternatif | Alasan Ditolak | |
||||
|-----------|---------------| |
||||
| Hanya hostname | Berubah jika komputer di-rename | |
||||
| Hanya machine_id | Berubah jika clean install OS | |
||||
| Generate UUID baru per install | Tidak bisa merge laporan lama | |
||||
| Kombinasi hostname+mac UNIQUE | Terlalu ketat — gagal jika ganti NIC | |
||||
|
||||
--- |
||||
|
||||
## Konsekuensi |
||||
|
||||
- Jika serial number berubah (ganti motherboard), bisa membuat asset baru — acceptable |
||||
- Jika MAC berubah (ganti NIC), fallback ke machine_id atau hostname |
||||
- VM clone bisa saling override jika MAC sama — perlu filter `asset_type=vm` terpisah |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
# ADR-002 — Split Asset dengan Hardware Campur |
||||
|
||||
**Status:** Accepted |
||||
**Tanggal:** 2026-06-22 |
||||
**Penulis:** Tim IT GIS Portal |
||||
|
||||
--- |
||||
|
||||
## Konteks |
||||
|
||||
Ditemukan kasus di `DESKTOP-RRJ9G01`: satu username/hostname tapi laptop dan PC berbeda digunakan bergantian. Agent melaporkan hardware yang berbeda dari hostname yang sama, menghasilkan confusing asset record dengan data hardware yang terus berganti. |
||||
|
||||
Masalah spesifik: |
||||
- `machine_id` UNIQUE constraint — jika mesin lain register hostname yang sama, constraint error |
||||
- Riwayat snapshot menunjukkan hardware yang inkonsisten (laptop Dell vs PC Gigabyte) |
||||
- CMDB tidak bisa menunjukkan "kamu punya 2 device: laptop + PC" |
||||
|
||||
--- |
||||
|
||||
## Keputusan |
||||
|
||||
1. **Hapus UNIQUE constraint `machine_id`** (migration 003) — `machine_id` bukan identifier yang reliable |
||||
2. **Buat script split** untuk memisahkan asset dengan hardware campur |
||||
3. **Gunakan `smbios_uuid` sebagai key pembeda** hardware fisik yang berbeda |
||||
|
||||
**Script split:** `docker exec geonet-collector python /app/scripts/split_asset_by_hardware.py <uuid>` |
||||
|
||||
Script ini: |
||||
- Deteksi snapshot dengan `smbios_uuid` berbeda dari satu asset |
||||
- Buat asset record baru untuk hardware lain |
||||
- Pindahkan snapshot yang relevan ke asset baru |
||||
- Set `hostname_alias` atau `merged_from` di metadata |
||||
|
||||
--- |
||||
|
||||
## Implementasi |
||||
|
||||
Pilot: `DESKTOP-RRJ9G01` — split menjadi: |
||||
- Asset A: Laptop Dell Inspiron (smbios_uuid: 4C4C4544...) |
||||
- Asset B: PC Gigabyte (smbios_uuid: berbeda) |
||||
|
||||
Keduanya tetap terhubung ke user yang sama via `asset_user_assignments`. |
||||
|
||||
--- |
||||
|
||||
## Alternatif yang Ditolak |
||||
|
||||
| Alternatif | Alasan Ditolak | |
||||
|-----------|---------------| |
||||
| UNIQUE (hostname, smbios_uuid) | Terlalu ketat untuk edge cases lain | |
||||
| Biarkan saja (merge semua) | CMDB tidak akurat — tidak tahu user punya 2 device | |
||||
| Require manual entry per device | Tidak scalable, agent tidak tahu mana yang "utama" | |
||||
|
||||
--- |
||||
|
||||
## Konsekuensi |
||||
|
||||
- `machine_id` tidak lagi UNIQUE — tidak bisa dijadikan identifier tunggal |
||||
- Asset record yang lama dengan hardware campur perlu di-split manual (per kasus) |
||||
- Script split tersedia tapi perlu dijalankan manual oleh admin |
||||
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
# ADR-003 — Dokumen Asset di QNAP Object Storage |
||||
|
||||
**Status:** Accepted |
||||
**Tanggal:** 2026-06-22 |
||||
**Penulis:** Tim IT GIS Portal |
||||
|
||||
--- |
||||
|
||||
## Konteks |
||||
|
||||
Tim IT perlu melampirkan dokumen fisik ke asset CMDB: |
||||
- Invoice pembelian hardware |
||||
- Service report perbaikan |
||||
- Surat complain/garansi |
||||
- Certificate of authenticity |
||||
|
||||
Dokumen-dokumen ini berupa file (PDF, gambar, Word) yang tidak cocok disimpan di PostgreSQL sebagai blob. Perlu penyimpanan file eksternal yang terintegrasi. |
||||
|
||||
--- |
||||
|
||||
## Keputusan |
||||
|
||||
Gunakan **QNAP QuObjects Object Storage (S3-compatible)** dengan bucket terpisah `geonetagent-documents`. |
||||
|
||||
**Alasan memilih QNAP OSS:** |
||||
- Infrastruktur QNAP sudah ada (10.100.1.10) |
||||
- S3-compatible API — bisa pakai library standar (`boto3`, Laravel S3 driver) |
||||
- Tidak perlu bayar cloud storage |
||||
- Data on-premise — tidak keluar jaringan internal |
||||
- Bucket `super-apps` sudah terbukti berfungsi di proyek lain (mysupperapps_FE_PWA) |
||||
|
||||
**Bucket:** `geonetagent-documents` (terpisah dari `super-apps` untuk isolasi) |
||||
|
||||
**Struktur path di bucket:** |
||||
``` |
||||
geonetagent-documents/ |
||||
├── invoices/<asset_id>/<filename> |
||||
├── service-reports/<asset_id>/<filename> |
||||
└── complaints/<asset_id>/<filename> |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Schema DB (Migration 005 — Draft, Belum Apply) |
||||
|
||||
```sql |
||||
CREATE TABLE asset_invoices ( |
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
||||
asset_id UUID REFERENCES assets(id), |
||||
invoice_number VARCHAR, |
||||
vendor VARCHAR, |
||||
purchase_date DATE, |
||||
amount NUMERIC(12,2), |
||||
currency VARCHAR DEFAULT 'IDR', |
||||
warranty_until DATE, |
||||
notes TEXT, |
||||
created_at TIMESTAMPTZ DEFAULT NOW() |
||||
); |
||||
|
||||
CREATE TABLE document_attachments ( |
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
||||
related_type VARCHAR, -- 'invoice', 'ticket', 'asset' |
||||
related_id UUID, |
||||
filename VARCHAR NOT NULL, |
||||
storage_path VARCHAR NOT NULL, -- path di QNAP bucket |
||||
file_size_bytes INTEGER, |
||||
mime_type VARCHAR, |
||||
uploaded_by VARCHAR, |
||||
uploaded_at TIMESTAMPTZ DEFAULT NOW() |
||||
); |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Implementasi (Fase 3) |
||||
|
||||
1. Buat bucket `geonetagent-documents` di QNAP |
||||
2. Apply migration 005 ke DB |
||||
3. Laravel: upload via S3 driver → simpan path di `document_attachments` |
||||
4. Laravel: download via presigned URL atau proxy route |
||||
5. UI: attach file ke asset di halaman detail |
||||
|
||||
--- |
||||
|
||||
## Alternatif yang Ditolak |
||||
|
||||
| Alternatif | Alasan Ditolak | |
||||
|-----------|---------------| |
||||
| PostgreSQL bytea | Tidak scalable untuk file besar, backup berat | |
||||
| NFS mount Laravel | Coupling ke infrastruktur NFS, sudah migrasi dari NFS di proyek lain | |
||||
| Cloud storage (S3 AWS, GCS) | Data harus on-premise, ada biaya tambahan | |
||||
| File system lokal server | Tidak HA, coupling ke satu server | |
||||
|
||||
--- |
||||
|
||||
## Konsekuensi |
||||
|
||||
- Laravel butuh QNAP OSS credentials di `.env` — **jangan commit ke repo** |
||||
- Bucket perlu dikonfigurasi access control — private (presigned URL untuk download) |
||||
- Jika QNAP down: upload/download dokumen tidak tersedia, tapi CMDB tetap berfungsi |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
# Architecture Decision Records (ADR) |
||||
|
||||
ADR mendokumentasikan keputusan arsitektur penting, alasan di baliknya, dan alternatif yang dipertimbangkan. |
||||
|
||||
**Aturan:** ADR tidak boleh dihapus. Jika keputusan berubah, buat ADR baru. |
||||
|
||||
--- |
||||
|
||||
| ADR | Judul | Status | |
||||
|-----|-------|--------| |
||||
| [ADR-001](ADR-001-agent-asset-identity.md) | Strategi Identitas Asset | Accepted | |
||||
| [ADR-002](ADR-002-asset-split-by-hardware.md) | Split Asset Hardware Campur | Accepted | |
||||
| [ADR-003](ADR-003-asset-documents-qnap.md) | Dokumen Asset di QNAP Object Storage | Accepted | |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
# API — Error Codes |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## HTTP Status Codes |
||||
|
||||
| Code | Keterangan | Penyebab Umum | |
||||
|------|------------|---------------| |
||||
| 200 | OK | Request berhasil | |
||||
| 201 | Created | Asset baru dibuat | |
||||
| 422 | Unprocessable Entity | Payload tidak valid (field hilang/tipe salah) | |
||||
| 401 | Unauthorized | Token tidak ada atau salah | |
||||
| 403 | Forbidden | Token valid tapi tidak punya akses | |
||||
| 404 | Not Found | Resource tidak ditemukan | |
||||
| 503 | Service Unavailable | DB tidak bisa diakses | |
||||
|
||||
--- |
||||
|
||||
## Format Error 422 (Validation) |
||||
|
||||
```json |
||||
{ |
||||
"detail": [ |
||||
{ |
||||
"loc": ["body", "hostname"], |
||||
"msg": "field required", |
||||
"type": "value_error.missing" |
||||
}, |
||||
{ |
||||
"loc": ["body", "system", "ram_gb"], |
||||
"msg": "value is not a valid float", |
||||
"type": "type_error.float" |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Format Error Lainnya |
||||
|
||||
```json |
||||
{ |
||||
"detail": "Error message string" |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## PowerShell Agent Error Handling |
||||
|
||||
Agent tidak retry otomatis jika gagal. Error di-log ke `%ProgramData%\GeoNetAgent\logs\`. Scheduled Task akan retry di jadwal berikutnya (3 jam). |
||||
|
||||
Untuk debug manual: |
||||
```powershell |
||||
& "$env:ProgramData\GeoNetAgent\UserAgent.ps1" |
||||
# Cek output di terminal |
||||
``` |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
# API Overview |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Services |
||||
|
||||
| Service | Base URL | Auth | Status | |
||||
|---------|----------|------|--------| |
||||
| FastAPI Collector | `https://collector.gisportal.id` | Bearer token | ✅ Running | |
||||
| FastAPI Collector (internal) | `http://10.100.1.24:8000` | Bearer token | ✅ Running | |
||||
| Laravel Portal | `https://portal.geonet.internal` | Session/Sanctum | ❌ Belum scaffold | |
||||
|
||||
--- |
||||
|
||||
## Auth |
||||
|
||||
Semua endpoint FastAPI memerlukan header: |
||||
|
||||
``` |
||||
Authorization: Bearer <agent_token> |
||||
``` |
||||
|
||||
Token dikonfigurasi di `.env` collector (`AGENT_BEARER_TOKEN`). |
||||
|
||||
--- |
||||
|
||||
## Format Response Standard |
||||
|
||||
**Success:** |
||||
```json |
||||
{ |
||||
"status": "ok", |
||||
"data": { ... } |
||||
} |
||||
``` |
||||
|
||||
**Error:** |
||||
```json |
||||
{ |
||||
"detail": "Error message" |
||||
} |
||||
``` |
||||
|
||||
**Validation Error (422):** |
||||
```json |
||||
{ |
||||
"detail": [ |
||||
{ |
||||
"loc": ["body", "hostname"], |
||||
"msg": "field required", |
||||
"type": "value_error.missing" |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Endpoints Ringkas |
||||
|
||||
| Method | Path | Keterangan | Auth | |
||||
|--------|------|------------|------| |
||||
| `POST` | `/api/v1/agent/report` | Laporan dari agent | Bearer | |
||||
| `GET` | `/api/v1/assets` | List asset | Bearer | |
||||
| `GET` | `/api/v1/assets/{id}` | Detail asset | Bearer | |
||||
| `GET` | `/api/v1/assets/{id}/snapshots` | History snapshot | Bearer | |
||||
| `GET` | `/api/v1/assets/{id}/software` | Software terinstall | Bearer | |
||||
| `GET` | `/health` | Health check | Tidak perlu | |
||||
| `GET` | `/ui/` | Web UI CMDB | Tidak perlu (LAN only) | |
||||
| `GET` | `/docs` | Swagger UI | Tidak perlu | |
||||
|
||||
--- |
||||
|
||||
## Interactive Docs |
||||
|
||||
Swagger UI tersedia di: |
||||
- `https://collector.gisportal.id/docs` |
||||
- `http://10.100.1.24:8000/docs` |
||||
|
||||
OpenAPI JSON: |
||||
- `https://collector.gisportal.id/openapi.json` |
||||
|
||||
--- |
||||
|
||||
## Contoh Request (cURL) |
||||
|
||||
```bash |
||||
# Health check |
||||
curl https://collector.gisportal.id/health |
||||
|
||||
# List assets |
||||
curl -H "Authorization: Bearer <token>" \ |
||||
https://collector.gisportal.id/api/v1/assets |
||||
|
||||
# Post laporan (dari file sample) |
||||
curl -X POST \ |
||||
-H "Authorization: Bearer <token>" \ |
||||
-H "Content-Type: application/json" \ |
||||
-d @samples/agent-reports/sample-report-v0.1.10.json \ |
||||
https://agent.gisportal.id/api/v1/agent/report |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Versioning |
||||
|
||||
Semua endpoint menggunakan prefix `/api/v1/`. API v2 akan dibuat jika ada breaking changes — v1 tetap berjalan paralel. |
||||
@ -0,0 +1,184 @@
@@ -0,0 +1,184 @@
|
||||
# API — FastAPI Collector Service |
||||
|
||||
> **Base URL:** `https://collector.gisportal.id` / `https://agent.gisportal.id` |
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
> **Auth:** `Authorization: Bearer <agent_token>` (semua endpoint kecuali /health dan /ui/) |
||||
|
||||
--- |
||||
|
||||
## GET /health |
||||
|
||||
Health check. Tidak butuh auth. |
||||
|
||||
**Response 200:** |
||||
```json |
||||
{"status": "ok", "db": "connected"} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## POST /api/v1/agent/report |
||||
|
||||
Menerima laporan hardware/software dari PowerShell agent. |
||||
|
||||
**Header:** `Authorization: Bearer <agent_token>` |
||||
|
||||
**Request body (ringkas):** |
||||
```json |
||||
{ |
||||
"schema_version": "1.0", |
||||
"agent_version": "0.1.10-phase0", |
||||
"collected_at": "2026-06-22T09:00:00+07:00", |
||||
"hostname": "DESKTOP-RRJ9G01", |
||||
"machine_id": "3709baa5-5265-4e4d-9a81-fb8c8927bb3b", |
||||
"smbios_uuid": "4C4C4544-004C-5610-805A-B2C04F4A3432", |
||||
"serial_number": "2LVZJ42", |
||||
"asset_type": "endpoint", |
||||
"logged_in_user": "DOMAIN\\username", |
||||
"system": { |
||||
"manufacturer": "Dell Inc.", |
||||
"model": "Inspiron 7447", |
||||
"os_name": "Microsoft Windows 11 Pro", |
||||
"os_build": "10.0.26200", |
||||
"cpu_model": "Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz", |
||||
"ram_gb": 15.9, |
||||
"gpu_model": "NVIDIA GeForce GTX 850M", |
||||
"form_factor": "Laptop", |
||||
"last_boot_at": "2026-06-20T08:42:51+07:00" |
||||
}, |
||||
"cpu": { |
||||
"model": "Intel Core i7-4710HQ", |
||||
"cores": 4, |
||||
"logical_processors": 8, |
||||
"max_clock_mhz": 2500, |
||||
"l2_cache_kb": 1024, |
||||
"l3_cache_kb": 6144, |
||||
"architecture": "x64" |
||||
}, |
||||
"gpus": [ |
||||
{"name": "NVIDIA GeForce GTX 850M", "dedicated_memory_mb": 4054} |
||||
], |
||||
"disks": [ |
||||
{"drive_letter": "C:", "total_gb": 188.7, "free_gb": 3.1, "pct_used": 98.3, "file_system": "NTFS", "model": "ADATA SU650"} |
||||
], |
||||
"physical_disks": [ |
||||
{"model": "ADATA SU650", "serial": "2K3620065948", "size_gb": 223.6, "media_type": "SSD", "bus_type": "SATA"} |
||||
], |
||||
"memory_summary": { |
||||
"total_gb": 16, |
||||
"module_count": 2, |
||||
"layout": "2x8GB", |
||||
"upgradeable": false |
||||
}, |
||||
"memory_modules": [ |
||||
{"slot": "DIMM_A", "capacity_gb": 8, "speed_mhz": 1600, "memory_type": 24, "form_factor": "sodimm"} |
||||
], |
||||
"network": [ |
||||
{"adapter_name": "Realtek PCIe GBE", "mac_address": "B8:2A:72:FA:17:FF", "ip_address": "192.168.0.33", "is_primary": true, "is_built_in": true} |
||||
], |
||||
"asset_identity": { |
||||
"primary_physical_mac": "B8:2A:72:FA:17:FF", |
||||
"merge_key": "mac:B8:2A:72:FA:17:FF", |
||||
"recommended_match_order": ["serial_number", "smbios_uuid", "primary_physical_mac", "machine_id", "hostname"] |
||||
}, |
||||
"peripherals": [ |
||||
{"name": "Dell BT Adapter", "device_class": "Bluetooth", "connection": "usb", "is_external": true} |
||||
], |
||||
"health": { |
||||
"bsod_count_30d": 0, |
||||
"pending_reboot": false, |
||||
"last_patch_installed": "2026-02-27" |
||||
}, |
||||
"software": [ |
||||
{"name": "Google Chrome", "version": "120.0.6099.130", "publisher": "Google LLC", "install_date": "2024-01-15"} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
**Response 200:** |
||||
```json |
||||
{ |
||||
"status": "ok", |
||||
"asset_id": "uuid-of-asset", |
||||
"action": "updated" |
||||
} |
||||
``` |
||||
|
||||
**Response 401:** Token salah |
||||
**Response 422:** Payload tidak valid (field wajib hilang atau tipe salah) |
||||
|
||||
--- |
||||
|
||||
## GET /api/v1/assets |
||||
|
||||
List asset dengan filter opsional. |
||||
|
||||
**Query params:** |
||||
- `?hostname=<hostname>` — filter by hostname (partial match) |
||||
- `?status=active` — filter by status |
||||
- `?page=1&per_page=20` — pagination |
||||
|
||||
**Response 200:** |
||||
```json |
||||
{ |
||||
"total": 42, |
||||
"page": 1, |
||||
"per_page": 20, |
||||
"items": [ |
||||
{ |
||||
"id": "uuid", |
||||
"hostname": "DESKTOP-RRJ9G01", |
||||
"asset_type": "endpoint", |
||||
"status": "active", |
||||
"last_seen_at": "2026-06-22T09:00:00+07:00", |
||||
"cpu_model": "Intel Core i7-4710HQ", |
||||
"ram_gb": 15.9, |
||||
"os_name": "Microsoft Windows 11 Pro", |
||||
"form_factor": "Laptop" |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## GET /api/v1/assets/{id} |
||||
|
||||
Detail asset termasuk snapshot terbaru. |
||||
|
||||
**Response 200:** |
||||
```json |
||||
{ |
||||
"id": "uuid", |
||||
"hostname": "DESKTOP-RRJ9G01", |
||||
"asset_type": "endpoint", |
||||
"serial_number": "2LVZJ42", |
||||
"smbios_uuid": "4C4C...", |
||||
"status": "active", |
||||
"last_seen_at": "2026-06-22T09:00:00+07:00", |
||||
"latest_snapshot": { ... full payload ... } |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## GET /api/v1/assets/{id}/snapshots |
||||
|
||||
History snapshot untuk asset. |
||||
|
||||
**Query params:** `?limit=10` |
||||
|
||||
**Response 200:** Array snapshot objects dengan `collected_at` dan `agent_version`. |
||||
|
||||
--- |
||||
|
||||
## GET /api/v1/assets/{id}/software |
||||
|
||||
Software terinstall (dari snapshot terbaru). |
||||
|
||||
**Response 200:** |
||||
```json |
||||
[ |
||||
{"name": "Google Chrome", "version": "120.0.6099.130", "publisher": "Google LLC", "install_date": "2024-01-15"} |
||||
] |
||||
``` |
||||
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
# Architecture — Database Flow |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Ownership Write/Read |
||||
|
||||
``` |
||||
FastAPI Collector ──────────► PostgreSQL ◄──────── Laravel Portal |
||||
(write ingress otomatis) geonetagent (read + manual CRUD) |
||||
- agent/report upsert - asset list/detail |
||||
- Proxmox sync (Fase 3) - license manage |
||||
- software scan - document upload |
||||
``` |
||||
|
||||
**Aturan:** FastAPI = satu-satunya writer untuk data otomatis. Laravel = presentasi + manual entry. |
||||
|
||||
--- |
||||
|
||||
## Alur Write — Laporan Agent |
||||
|
||||
```mermaid |
||||
flowchart TD |
||||
A[Agent POST payload] --> B[FastAPI: validate schema] |
||||
B --> C{asset_id\nresolution\nADR-001} |
||||
C -->|serial_number| D[UPSERT assets\nWHERE serial_number=?] |
||||
C -->|smbios_uuid fallback| E[UPSERT assets\nWHERE smbios_uuid=?] |
||||
C -->|MAC fallback| F[UPSERT assets\nWHERE primary_mac=?] |
||||
C -->|machine_id fallback| G[UPSERT assets\nWHERE machine_id=?] |
||||
C -->|hostname last resort| H[INSERT/UPSERT assets\nWHERE hostname=?] |
||||
D & E & F & G & H --> I[UPDATE assets.last_seen_at] |
||||
I --> J[INSERT asset_snapshots\npayload=full JSON] |
||||
J --> K[UPSERT software_installations\nper collected_at] |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Alur Read — Collector UI |
||||
|
||||
``` |
||||
GET /api/v1/assets |
||||
→ SELECT a.*, v.cpu_model, v.ram_gb, v.gpu_model, v.os_name |
||||
FROM assets a |
||||
LEFT JOIN v_asset_hardware_latest v ON v.asset_id = a.id |
||||
WHERE a.deleted_at IS NULL |
||||
ORDER BY a.last_seen_at DESC |
||||
|
||||
GET /api/v1/assets/{id}/snapshots |
||||
→ SELECT * FROM asset_snapshots WHERE asset_id = ? |
||||
ORDER BY collected_at DESC LIMIT 10 |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Alur Read — Laravel (Fase 1) |
||||
|
||||
``` |
||||
Laravel Eloquent: |
||||
Asset::with(['latestSnapshot', 'location', 'primaryUser']) |
||||
->paginate(20) |
||||
``` |
||||
|
||||
Laravel menggunakan koneksi **read-only** ke DB (rekomendasi — belum diimplementasi). Saat ini masih credential yang sama. |
||||
|
||||
--- |
||||
|
||||
## View `v_asset_hardware_latest` |
||||
|
||||
View yang merangkum snapshot terbaru per asset (migration 004): |
||||
|
||||
```sql |
||||
CREATE VIEW v_asset_hardware_latest AS |
||||
SELECT DISTINCT ON (asset_id) |
||||
asset_id, |
||||
payload->>'os_name' AS os_name, |
||||
(payload->'system'->>'cpu_model') AS cpu_model, |
||||
(payload->'system'->>'ram_gb')::float AS ram_gb, |
||||
(payload->'system'->>'gpu_model') AS gpu_model, |
||||
collected_at |
||||
FROM asset_snapshots |
||||
ORDER BY asset_id, collected_at DESC; |
||||
``` |
||||
|
||||
> **Catatan:** Field hardware ada di path `payload->system->*` di snapshot lama, dan di `payload->*` (root) untuk payload baru (v0.1.9+). View mungkin perlu diupdate sesuai schema terbaru. |
||||
|
||||
--- |
||||
|
||||
## Transaction Boundary |
||||
|
||||
FastAPI menggunakan single DB session per request dengan auto-commit. Tidak ada distributed transaction. |
||||
|
||||
Jika partial write terjadi (mis. `assets` berhasil upsert tapi `asset_snapshots` gagal): |
||||
- Laporan berikutnya (3 jam kemudian) akan retry — idempotent |
||||
- Tidak ada rollback manual yang diperlukan |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
# Architecture — Deployment Architecture |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Topology |
||||
|
||||
``` |
||||
Endpoint Windows (Laptop/PC) |
||||
│ HTTPS POST /api/v1/agent/report |
||||
▼ |
||||
┌─────────────────────────────────────────────┐ |
||||
│ Nginx Reverse Proxy (10.100.1.24) │ |
||||
│ /etc/nginx/conf.d/production/ │ |
||||
│ - collector.conf → agent.gisportal.id │ |
||||
│ → collector.gisportal.id │ |
||||
└──────────────────┬──────────────────────────┘ |
||||
│ proxy_pass :18000 |
||||
▼ |
||||
┌──────────────────────┐ |
||||
│ FastAPI Collector │ |
||||
│ Docker container │ |
||||
│ geonet-collector │ |
||||
│ Port: 18000 │ |
||||
└──────────┬───────────┘ |
||||
│ |
||||
▼ |
||||
┌──────────────────────┐ |
||||
│ PostgreSQL 12 │ |
||||
│ 10.100.1.25 │ |
||||
│ DB: geonetagent │ |
||||
└──────────────────────┘ |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Container Info |
||||
|
||||
| Container | Image | Port | Stack Path | |
||||
|-----------|-------|------|------------| |
||||
| `geonet-collector` | Python 3.11 (FastAPI) | 18000 | `/opt/stacks/geonetagent-collector/` | |
||||
|
||||
--- |
||||
|
||||
## Nginx Config (collector.conf) |
||||
|
||||
```nginx |
||||
# agent.gisportal.id — agent POST + web install |
||||
server { |
||||
server_name agent.gisportal.id; |
||||
|
||||
location /api/ { |
||||
proxy_pass http://127.0.0.1:18000; |
||||
} |
||||
|
||||
location /install/ { |
||||
ssi off; # WAJIB — agar $env: tidak terpotong di file .ps1 |
||||
alias /var/www/agent-install/; |
||||
} |
||||
} |
||||
|
||||
# collector.gisportal.id — read UI + API |
||||
server { |
||||
server_name collector.gisportal.id; |
||||
|
||||
location / { |
||||
proxy_pass http://127.0.0.1:18000; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Web Install Static Files |
||||
|
||||
File agent yang di-serve di `agent.gisportal.id/install/`: |
||||
|
||||
``` |
||||
/var/www/agent-install/ |
||||
├── GeoNetAgent-Install.ps1 — Entry point user (2 baris PS) |
||||
├── bootstrap.ps1 |
||||
├── Install-GeoNetAgent.ps1 |
||||
├── manifest.json — Daftar file + versi (untuk auto-update) |
||||
├── config.json — Config default (token, URL) |
||||
└── lib/ |
||||
├── Agent-InstallCore.ps1 |
||||
├── Build-GeoNetReportPayload.ps1 |
||||
└── ... (semua file lib/) |
||||
``` |
||||
|
||||
> File ini di-publish via `.\infra\collector\publish_agent_install.ps1` |
||||
|
||||
--- |
||||
|
||||
## Docker Compose (Collector) |
||||
|
||||
```yaml |
||||
services: |
||||
geonet-collector: |
||||
build: ./collector-src |
||||
ports: |
||||
- "18000:18000" |
||||
env_file: .env |
||||
restart: unless-stopped |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Firewall |
||||
|
||||
- Port 443/80: public (HTTPS/HTTP via nginx) |
||||
- Port 18000: internal bind (127.0.0.1) — tidak expose langsung |
||||
- Port 8000: allow dari subnet 10.x dan 192.168.x (internal UI HTTP direct) |
||||
- PostgreSQL 5432: tidak expose, hanya dari container dan server 10.100.1.24 |
||||
|
||||
--- |
||||
|
||||
## Lihat Juga |
||||
|
||||
- `infra/nginx/README.md` — detail nginx config |
||||
- `infra/collector/` — deploy scripts |
||||
- `docs/deployment.md` — step-by-step deploy |
||||
- `docs/server.md` — SSH commands |
||||
@ -0,0 +1,199 @@
@@ -0,0 +1,199 @@
|
||||
# Architecture — Folder Structure |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Root Monorepo |
||||
|
||||
``` |
||||
GeoNetAgent/ |
||||
├── README.md — Ringkasan proyek + quick start |
||||
├── AGENTS.md — Aturan AI agent |
||||
├── .env.example — Template env vars |
||||
├── .gitignore — Git ignore rules |
||||
├── perintah.md — Protokol sync dokumentasi (wajib akhir sesi) |
||||
│ |
||||
├── agent/ — PowerShell agent (deploy ke endpoint Windows) |
||||
├── collector/ — FastAPI collector service |
||||
├── infra/ — Infrastruktur (Nginx, DB, Docker, QNAP) |
||||
├── samples/ — Sample payload untuk testing |
||||
├── docs/ — Dokumentasi lengkap ⭐ |
||||
├── .cursor/ — AI context (blueprint, schema, API, progress) |
||||
└── scripts/ — Utility scripts (opsional) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## `agent/` — PowerShell Agent |
||||
|
||||
``` |
||||
agent/ |
||||
├── README.md — Cara install (user + admin) |
||||
├── Collect-GeoNetReport.ps1 — Entry point collect data |
||||
├── Send-GeoNetReport.ps1 — Entry point kirim laporan |
||||
├── UserAgent.ps1 — Script user: cek update + kirim laporan |
||||
├── Install-GeoNetAgent.ps1 — Installer: setup file + scheduled task |
||||
├── VERSION — Versi agent saat ini (mis. v0.1.10-phase0) |
||||
├── config.example.json — Template config agent |
||||
├── config.json — Config aktif (tidak di-commit ke repo) |
||||
│ |
||||
├── lib/ — Modul sensor (satu file per sensor) |
||||
│ ├── Agent-InstallCore.ps1 — Inti proses instalasi |
||||
│ ├── Build-GeoNetReportPayload.ps1 — Assemble full JSON payload |
||||
│ ├── Get-AssetIdentity.ps1 — Resolve agent_id (ADR-001) |
||||
│ ├── Get-CpuInfo.ps1 — CPU: cores, clock, cache, arch |
||||
│ ├── Get-DiskInfo.ps1 — Logical disk (C:, D:, dll) |
||||
│ ├── Get-DisplaySettings.ps1 — Monitor EDID, resolusi |
||||
│ ├── Get-GpuInfo.ps1 — GPU model + dedicated memory |
||||
│ ├── Get-HealthInfo.ps1 — Disk usage %, battery |
||||
│ ├── Get-MemoryInfo.ps1 — RAM GB, DDR type, part no |
||||
│ ├── Get-NetworkInfo.ps1 — NIC, MAC, IP, is_built_in |
||||
│ ├── Get-PeripheralsInfo.ps1 — USB/BT/audio/monitor, is_external |
||||
│ ├── Get-PhysicalDiskInfo.ps1 — Physical disk: model, serial, type |
||||
│ ├── Get-SoftwareInfo.ps1 — Installed software list |
||||
│ ├── Get-SystemInfo.ps1 — OS, manufacturer, model, BIOS |
||||
│ ├── Send-CollectorReport.ps1 — HTTP POST ke collector |
||||
│ ├── Update-GeoNetAgent.ps1 — Auto-update dari manifest server |
||||
│ └── Format-*.ps1 — Utility formatting |
||||
│ |
||||
├── gpo/ — GPO startup scripts (rollout massal) |
||||
│ └── README.md |
||||
│ |
||||
└── install/ — File yang di-serve di agent.gisportal.id/install/ |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## `collector/` — FastAPI Service |
||||
|
||||
``` |
||||
collector/ |
||||
├── pyproject.toml — Python project config + dependencies |
||||
├── app/ |
||||
│ ├── main.py — FastAPI app init, lifespan, mount /ui/ |
||||
│ ├── config.py — Settings dari .env (pydantic-settings) |
||||
│ ├── api/ — Route handlers |
||||
│ │ ├── agent.py — POST /api/v1/agent/report |
||||
│ │ ├── assets.py — GET /api/v1/assets (read) |
||||
│ │ └── health.py — GET /health |
||||
│ ├── schemas/ — Pydantic schemas |
||||
│ ├── services/ — Business logic (asset resolution, upsert) |
||||
│ ├── db/ — DB session + models |
||||
│ └── core/ — Middleware, dependencies |
||||
├── static/ — UI files (HTML/JS/CSS untuk /ui/) |
||||
├── tests/ — pytest |
||||
└── docs/ |
||||
└── API.md — API documentation (konsumsi API) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## `infra/` — Infrastruktur |
||||
|
||||
``` |
||||
infra/ |
||||
├── collector/ — Deploy scripts collector |
||||
│ ├── push_collector.ps1 — Deploy collector ke server (Windows) |
||||
│ ├── push_collector.sh — Deploy collector ke server (Linux) |
||||
│ ├── publish_agent_install.ps1 — Publish agent ke web install (Windows) |
||||
│ ├── publish_agent_install.sh — Publish agent ke web install (Linux) |
||||
│ └── deploy_nginx.sh — Deploy nginx config |
||||
├── db/ — SQL migration files |
||||
│ ├── README.md — Cara apply migration |
||||
│ ├── 001_initial_schema.sql |
||||
│ ├── 002_disk_model.sql |
||||
│ ├── 003_drop_machine_id_unique.sql |
||||
│ ├── 004_view_hardware_latest.sql |
||||
│ └── 005_asset_documents.sql — Draft, belum apply |
||||
├── nginx/ — Nginx config templates |
||||
│ └── README.md |
||||
└── qnap/ — QNAP Object Storage config |
||||
└── README.md |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## `docs/` — Dokumentasi Lengkap |
||||
|
||||
``` |
||||
docs/ |
||||
├── context-index.md ⭐ AI Entry Point |
||||
├── project-status.md |
||||
├── roadmap.md |
||||
├── todo.md |
||||
├── issues.md |
||||
├── environment.md |
||||
├── server.md |
||||
├── deployment.md |
||||
├── development.md |
||||
├── database.md |
||||
├── troubleshooting.md |
||||
├── monitoring.md |
||||
├── security.md |
||||
├── glossary.md |
||||
│ |
||||
├── architecture/ |
||||
│ ├── system-overview.md |
||||
│ ├── folder-structure.md (file ini) |
||||
│ ├── request-flow.md |
||||
│ ├── authentication-flow.md |
||||
│ ├── database-flow.md |
||||
│ └── deployment-architecture.md |
||||
│ |
||||
├── api/ |
||||
│ ├── overview.md |
||||
│ ├── service-collector.md |
||||
│ └── error-codes.md |
||||
│ |
||||
├── adr/ |
||||
│ ├── README.md |
||||
│ ├── ADR-001-agent-asset-identity.md |
||||
│ ├── ADR-002-asset-split-by-hardware.md |
||||
│ └── ADR-003-asset-documents-qnap.md |
||||
│ |
||||
├── diagrams/ |
||||
│ └── *.mmd |
||||
│ |
||||
├── onboarding/ |
||||
│ ├── first-day.md |
||||
│ ├── local-setup.md |
||||
│ └── coding-standard.md |
||||
│ |
||||
└── decisions/ |
||||
├── technology-stack.md |
||||
└── naming-convention.md |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## `.cursor/` — AI Context (Detail Teknis) |
||||
|
||||
``` |
||||
.cursor/ |
||||
├── context-index.md — Entry point lama (versi lama, masih relevan) |
||||
├── blueprint.md — Master blueprint + modul M1-M9 |
||||
├── progress.md — Checklist fase + milestone history |
||||
├── db-schema.md — Schema detail + ERD Mermaid |
||||
├── api-contracts.md — Kontrak API FastAPI + Laravel + webhook |
||||
├── repo-structure.md — Konvensi monorepo |
||||
├── me.md — Profil user + inventaris infrastruktur |
||||
├── server.md — SSH commands semua server |
||||
├── monitoring-architecture.md — Layer monitoring Zabbix/FastAPI/Laravel |
||||
├── agent-playbook.md — Workflow multi-tab koordinasi |
||||
├── adr/ — ADR files (kosong — ada di docs/adr/) |
||||
└── rules/ — Cursor rules (kosong — perlu diisi) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Konvensi Naming |
||||
|
||||
| Tipe | Konvensi | Contoh | |
||||
|------|----------|--------| |
||||
| Python file | `snake_case.py` | `agent_report.py` | |
||||
| PS1 file | `Verb-Noun.ps1` | `Get-CpuInfo.ps1`, `Send-CollectorReport.ps1` | |
||||
| SQL migration | `NNN_description.sql` | `003_drop_machine_id_unique.sql` | |
||||
| DB tabel | `snake_case` plural | `asset_snapshots`, `software_installations` | |
||||
| FastAPI route | `/api/v1/resource` kebab-case | `/api/v1/agent/report` | |
||||
| Doc file | `kebab-case.md` | `system-overview.md` | |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
# Architecture — Request Flow |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Alur Laporan Agent |
||||
|
||||
```mermaid |
||||
sequenceDiagram |
||||
participant PS as PowerShell Agent |
||||
participant NGX as Nginx (10.100.1.24) |
||||
participant COL as FastAPI Collector |
||||
participant PG as PostgreSQL |
||||
|
||||
PS->>PS: Scheduled Task setiap 3 jam |
||||
PS->>PS: Cek VERSION lokal vs manifest.json server |
||||
alt Versi server lebih baru |
||||
PS->>NGX: GET agent.gisportal.id/install/manifest.json |
||||
NGX-->>PS: manifest.json |
||||
PS->>NGX: Download file-file terbaru |
||||
PS->>PS: Replace file lokal |
||||
end |
||||
PS->>PS: Collect-GeoNetReport.ps1 (WMI/CIM scan) |
||||
PS->>NGX: POST agent.gisportal.id/api/v1/agent/report\nAuthorization: Bearer <token> |
||||
NGX->>COL: proxy_pass :18000 |
||||
COL->>COL: Validate schema + bearer token |
||||
COL->>COL: Resolve asset_id (ADR-001:\nserial → smbios_uuid → MAC → machine_id) |
||||
COL->>PG: UPSERT assets (hostname, last_seen_at, dll) |
||||
COL->>PG: INSERT asset_snapshots (payload JSON) |
||||
COL->>PG: UPSERT software_installations |
||||
COL-->>NGX: 200 OK {asset_id, status} |
||||
NGX-->>PS: 200 OK |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Alur User Buka UI (Fase 1) |
||||
|
||||
```mermaid |
||||
sequenceDiagram |
||||
participant IT as IT Admin (Browser) |
||||
participant NGX as Nginx |
||||
participant COL as FastAPI /ui/ |
||||
participant PG as PostgreSQL |
||||
|
||||
IT->>NGX: GET collector.gisportal.id/ui/ |
||||
NGX->>COL: proxy_pass :18000 |
||||
COL-->>IT: HTML/JS (static files) |
||||
|
||||
IT->>NGX: GET collector.gisportal.id/api/v1/assets |
||||
NGX->>COL: proxy_pass :18000 |
||||
COL->>PG: SELECT dari assets + v_asset_hardware_latest |
||||
PG-->>COL: asset data |
||||
COL-->>IT: JSON [{id, hostname, cpu_model, ram_gb, ...}] |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Idempotency |
||||
|
||||
Laporan agent bersifat idempotent: |
||||
- `UPSERT assets` berdasarkan `asset_id` (resolve ADR-001) |
||||
- Duplikat snapshot `(asset_id, collected_at)` diabaikan |
||||
- Software installations di-replace per `collected_at` |
||||
|
||||
--- |
||||
|
||||
## Error Handling |
||||
|
||||
| Skenario | Behavior | |
||||
|----------|----------| |
||||
| Token salah (401) | Collector return 401, agent log error, tidak retry otomatis | |
||||
| Schema invalid (422) | Collector return 422 + detail field errors | |
||||
| DB tidak bisa diakses | Collector return 503, agent log error | |
||||
| Timeout | Agent tidak retry — akan coba lagi di jadwal berikutnya (3 jam) | |
||||
| Duplikat `collected_at` | Diabaikan (idempotent), return 200 | |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
# Architecture — System Overview |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Visi |
||||
|
||||
GeoNetAgent adalah **portal IT + CMDB ringan yang melengkapi Zabbix**, bukan menggantikannya. |
||||
|
||||
- **Zabbix** = monitoring real-time, alerting, threshold |
||||
- **GeoNetAgent** = inventaris hardware/software point-in-time, manajemen aset, dokumen procurement |
||||
|
||||
--- |
||||
|
||||
## Diagram Sistem |
||||
|
||||
```mermaid |
||||
graph TB |
||||
subgraph ENDPOINTS["Endpoint Windows (Laptop/PC)"] |
||||
PS["PowerShell Agent\nScheduled Task 3 jam"] |
||||
end |
||||
|
||||
subgraph INFRA["Infrastruktur Existing"] |
||||
ZBX["Zabbix\n10.100.1.42"] |
||||
AD["Active Directory\n10.100.1.40"] |
||||
QNAP["QNAP NAS\n10.100.1.10\nObject Storage"] |
||||
end |
||||
|
||||
subgraph SERVER24["10.100.1.24 — App Server"] |
||||
NGX["Nginx\nSSL + Routing"] |
||||
COL["FastAPI Collector\n:18000"] |
||||
LRV["Laravel Portal\n(planned)"] |
||||
end |
||||
|
||||
subgraph SERVER25["10.100.1.25 — DB Server"] |
||||
PG[("PostgreSQL 12\ngeonetagent DB")] |
||||
end |
||||
|
||||
PS -->|"HTTPS POST /api/v1/agent/report\nBearer token"| NGX |
||||
NGX -->|"agent.gisportal.id"| COL |
||||
NGX -->|"collector.gisportal.id"| COL |
||||
COL --> PG |
||||
LRV --> PG |
||||
LRV -->|"Zabbix API (Fase 2)"| ZBX |
||||
ZBX -->|"webhook (Fase 2)"| LRV |
||||
LRV -->|"AD LDAP auth (Fase 2)"| AD |
||||
LRV -->|"S3 dokumen (Fase 3)"| QNAP |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Boundary System — Siapa Kerja Apa |
||||
|
||||
| Komponen | Owner Data | Write | Read | |
||||
|----------|-----------|-------|------| |
||||
| Uptime, CPU live, RAM live | Zabbix | Zabbix agents | Laravel via Zabbix API | |
||||
| Alert firing | Zabbix | Zabbix triggers | Laravel via webhook + API | |
||||
| Asset inventory (hardware) | PostgreSQL `assets` | FastAPI (agent), Laravel (manual) | Laravel UI | |
||||
| Hardware snapshot history | PostgreSQL `asset_snapshots` | FastAPI only | Laravel UI | |
||||
| Software inventory | PostgreSQL `software_installations` | FastAPI (scan) | Laravel UI | |
||||
| License entitlements | PostgreSQL `licenses` | Laravel manual | Laravel UI | |
||||
| Asset documents | PostgreSQL + QNAP S3 | Laravel upload | Laravel (presigned) | |
||||
| VM inventory | PostgreSQL `assets` type=vm | FastAPI Proxmox sync | Laravel UI | |
||||
|
||||
**Aturan emas:** FastAPI = **ingress & sync otomatis**. Laravel = **presentasi, enrichment manual, auth**. |
||||
|
||||
--- |
||||
|
||||
## Alur Data Utama |
||||
|
||||
### 1. Laporan Agent |
||||
|
||||
``` |
||||
[Scheduled Task SYSTEM, 3 jam] |
||||
→ PowerShell Collect-GeoNetReport.ps1 |
||||
→ WMI/CIM + network + software scan |
||||
→ Build JSON payload |
||||
→ POST agent.gisportal.id/api/v1/agent/report (HTTPS + Bearer) |
||||
→ Nginx → FastAPI Collector |
||||
→ Validate + resolve asset_id (ADR-001) |
||||
→ Upsert assets + insert asset_snapshots |
||||
→ Insert software_installations |
||||
``` |
||||
|
||||
### 2. User Buka Dashboard (Fase 1) |
||||
|
||||
``` |
||||
IT Admin → Browser → collector.gisportal.id/ui/ |
||||
→ FastAPI read API → PostgreSQL |
||||
→ Tampilkan list asset + hardware info |
||||
``` |
||||
|
||||
### 3. Zabbix Alert ke Portal (Fase 2) |
||||
|
||||
``` |
||||
Zabbix trigger PROBLEM |
||||
→ Media type webhook → POST Laravel /webhooks/zabbix |
||||
→ Normalize → insert tabel alerts |
||||
→ Notifikasi di portal IT |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Komponen Detail |
||||
|
||||
| Komponen | File | Status | |
||||
|----------|------|--------| |
||||
| Agent PowerShell | `agent/` | ✅ v0.1.10 | |
||||
| Web install | `agent.gisportal.id/install/` | ✅ Running | |
||||
| FastAPI Collector | `collector/` | ✅ Running | |
||||
| Collector UI | `collector.gisportal.id/ui/` | ✅ Running | |
||||
| PostgreSQL | `infra/db/` | ✅ Running (dev) | |
||||
| Nginx config | `infra/nginx/` | ✅ Running | |
||||
| Laravel Portal | `portal/` (planned) | ❌ Belum scaffold | |
||||
|
||||
--- |
||||
|
||||
## Lihat Juga |
||||
|
||||
- [`folder-structure.md`](folder-structure.md) — struktur monorepo |
||||
- [`request-flow.md`](request-flow.md) — alur request detail |
||||
- [`deployment-architecture.md`](deployment-architecture.md) — server topology |
||||
- `.cursor/blueprint.md` — visi lengkap + modul M1-M9 |
||||
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
# Context Index — GeoNetAgent ⭐ AI Entry Point |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
> **Versi Agent:** v0.1.10-phase0 |
||||
> **Fase Aktif:** Fase 1 — Foundation 🔄 |
||||
|
||||
--- |
||||
|
||||
## Filosofi |
||||
|
||||
Proyek ini menerapkan **AI Native Development**: |
||||
- Repository = sumber kebenaran utama (Single Source of Truth) |
||||
- AI tidak boleh mengandalkan riwayat percakapan |
||||
- Developer sering berpindah perangkat — konteks harus tersimpan di repo |
||||
- Implementasi tanpa dokumentasi = belum selesai |
||||
|
||||
--- |
||||
|
||||
## Urutan Membaca |
||||
|
||||
1. **File ini** — peta navigasi |
||||
2. `.cursor/blueprint.md` — visi, fase, batas sistem |
||||
3. `.cursor/progress.md` — fase aktif & DoD checklist |
||||
4. Dokumen spesifik task (lihat tabel di bawah) |
||||
5. `perintah.md` — wajib dibaca sebelum menyatakan selesai |
||||
|
||||
--- |
||||
|
||||
## Peta Dokumentasi |
||||
|
||||
### Dokumentasi di `docs/` |
||||
|
||||
| File | Isi | Baca Saat | |
||||
|------|-----|-----------| |
||||
| `docs/project-status.md` | Status modul, progress, known issues | Selalu | |
||||
| `docs/roadmap.md` | Fase & milestone | Planning | |
||||
| `docs/todo.md` | Next actions berprioritas | Sebelum coding | |
||||
| `docs/issues.md` | Known bugs + root cause | Debug | |
||||
| `docs/environment.md` | Env vars (tanpa secret) | Setup, deploy | |
||||
| `docs/server.md` | Server list, SSH, deploy commands | Deploy, infra | |
||||
| `docs/deployment.md` | Step-by-step deploy | Deploy | |
||||
| `docs/development.md` | Setup lokal, coding standard | Onboarding | |
||||
| `docs/database.md` | Schema PostgreSQL, tabel, migration | DB change | |
||||
| `docs/troubleshooting.md` | Debug common problems | Debug | |
||||
| `docs/monitoring.md` | Zabbix, collector health | Monitoring | |
||||
| `docs/security.md` | Credential management, rules | Security | |
||||
| `docs/glossary.md` | Kamus istilah | Reference | |
||||
|
||||
### Arsitektur (`docs/architecture/`) |
||||
|
||||
| File | Isi | |
||||
|------|-----| |
||||
| `system-overview.md` | Diagram sistem, boundary, komponen | |
||||
| `folder-structure.md` | Struktur monorepo | |
||||
| `request-flow.md` | Alur agent → collector → DB | |
||||
| `authentication-flow.md` | Auth agent token, Laravel session | |
||||
| `database-flow.md` | Write FastAPI, read Laravel | |
||||
| `deployment-architecture.md` | Docker, Nginx, server topology | |
||||
| `storage.md` | QNAP Object Storage (ADR 003) | |
||||
|
||||
### API (`docs/api/`) |
||||
|
||||
| File | Isi | |
||||
|------|-----| |
||||
| `overview.md` | Base URL, auth, format response | |
||||
| `service-collector.md` | FastAPI: agent/report, assets, snapshots | |
||||
| `service-portal.md` | Laravel: portal pages, documents, tickets (planned) | |
||||
| `error-codes.md` | Error format standar | |
||||
|
||||
### ADR (`docs/adr/`) |
||||
|
||||
| ADR | Keputusan | |
||||
|-----|-----------| |
||||
| `ADR-001-agent-asset-identity.md` | Identitas asset: serial → smbios_uuid → MAC → machine_id | |
||||
| `ADR-002-asset-split-by-hardware.md` | Split asset hardware campur (laptop + PC) | |
||||
| `ADR-003-asset-documents-qnap.md` | Dokumen procurement di QNAP Object Storage | |
||||
|
||||
### Konteks AI di `.cursor/` |
||||
|
||||
| File | Isi | |
||||
|------|-----| |
||||
| `.cursor/blueprint.md` | Master blueprint, visi, fase, modul M1-M9 | |
||||
| `.cursor/progress.md` | Checklist fase + milestone history | |
||||
| `.cursor/db-schema.md` | Schema PostgreSQL detail + ERD Mermaid | |
||||
| `.cursor/api-contracts.md` | Kontrak API FastAPI, Laravel, webhook | |
||||
| `.cursor/repo-structure.md` | Struktur monorepo + konvensi | |
||||
| `.cursor/me.md` | Profil user + inventaris infrastruktur | |
||||
| `.cursor/server.md` | SSH commands semua server | |
||||
| `.cursor/monitoring-architecture.md` | Layer monitoring Zabbix/FastAPI/Laravel | |
||||
| `.cursor/agent-playbook.md` | Workflow multi-tab koordinasi | |
||||
|
||||
--- |
||||
|
||||
## Quick Reference |
||||
|
||||
### URL Produksi |
||||
|
||||
| Fungsi | URL | |
||||
|--------|-----| |
||||
| Agent POST (internet) | `https://agent.gisportal.id/api/v1/agent/report` | |
||||
| Install agent | `https://agent.gisportal.id/install/` | |
||||
| Collector UI + read API | `https://collector.gisportal.id/ui/` | |
||||
| Internal HTTP | `http://10.100.1.24:8000/ui/` | |
||||
|
||||
### Deploy Commands |
||||
|
||||
```powershell |
||||
.\infra\collector\publish_agent_install.ps1 # publish agent |
||||
.\infra\collector\push_collector.ps1 # deploy collector |
||||
bash infra/collector/deploy_nginx.sh # deploy nginx |
||||
``` |
||||
|
||||
### Server |
||||
|
||||
| Server | IP | SSH | |
||||
|--------|----|-----| |
||||
| Reverse Proxy + Collector | 10.100.1.24 | `ssh -m hmac-sha1 root@10.100.1.24 -p 22` | |
||||
| PostgreSQL | 10.100.1.25 | `ssh -m hmac-sha1 root@10.100.1.25 -p 22` | |
||||
| AD/LDAP | 10.100.1.40 | `ssh -m hmac-sha1 Administrator@10.100.1.40 -p 22` | |
||||
| Zabbix | 10.100.1.42 | `ssh -m hmac-sha1 root@10.100.1.42 -p 22` | |
||||
| QNAP NAS | 10.100.1.10 | `ssh -i "$env:USERPROFILE\.ssh\id_ed25519" -o IdentitiesOnly=yes admin@10.100.1.10 -p 22` | |
||||
|
||||
--- |
||||
|
||||
## Fase & Status Saat Ini |
||||
|
||||
| Fase | Status | Blocker | |
||||
|------|--------|---------| |
||||
| Fase 0 — Blueprint | ✅ Selesai | - | |
||||
| Fase 1 — Foundation | 🔄 Aktif | Laravel asset index belum scaffold | |
||||
| Fase 2 — Zabbix integrasi | ⏳ | Menunggu Fase 1 DoD | |
||||
| Fase 3 — CMDB + Dokumen | ⏳ | Menunggu Fase 2 | |
||||
| Fase 4 — Scale | ⏳ | Menunggu Fase 3 | |
||||
|
||||
--- |
||||
|
||||
## Aturan Utama AI |
||||
|
||||
1. Baca `context-index.md` ini terlebih dahulu |
||||
2. Cek `.cursor/progress.md` — jangan implement fase yang belum waktunya |
||||
3. Setelah selesai: update progress.md + perintah.md |
||||
4. ADR tidak boleh dihapus — buat baru jika keputusan berubah |
||||
5. **Jangan commit secret** ke repository |
||||
@ -0,0 +1,204 @@
@@ -0,0 +1,204 @@
|
||||
# Database Documentation |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Overview |
||||
|
||||
| Item | Value | |
||||
|------|-------| |
||||
| DBMS | PostgreSQL 12+ | |
||||
| Host | 10.100.1.25 | |
||||
| DB Dev | `geonetagent_dev` | |
||||
| DB Prod | `geonetagent` | |
||||
| Timezone | `Asia/Jakarta` | |
||||
| PK Convention | UUID (`gen_random_uuid()`) | |
||||
| Timestamp | `timestamptz` | |
||||
| Credentials | `/root/geonetagent_dev.credentials` di server 10.100.1.25 | |
||||
|
||||
**Ownership:** |
||||
- **FastAPI Collector** = write ingress otomatis (agent reports, Proxmox sync) |
||||
- **Laravel Portal** = read + manual CRUD terbatas |
||||
|
||||
--- |
||||
|
||||
## ERD (MVP) |
||||
|
||||
```mermaid |
||||
erDiagram |
||||
locations ||--o{ assets : has |
||||
assets ||--o{ asset_snapshots : has |
||||
assets ||--o{ software_installations : has |
||||
assets ||--o{ asset_user_assignments : has |
||||
users ||--o{ asset_user_assignments : assigned |
||||
users ||--o{ license_assignments : has |
||||
licenses ||--o{ license_assignments : has |
||||
assets ||--o{ license_assignments : assigned |
||||
assets ||--o{ alerts : triggers |
||||
assets ||--o{ assets : parent_host |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Tabel Detail |
||||
|
||||
### `assets` |
||||
|
||||
Entitas utama — setiap device/VM/server. |
||||
|
||||
| Kolom | Tipe | Keterangan | |
||||
|-------|------|------------| |
||||
| `id` | UUID PK | `gen_random_uuid()` | |
||||
| `asset_type` | varchar | `endpoint`, `server`, `vm`, `proxmox_host`, `network_device`, `nas`, `gis_server` | |
||||
| `hostname` | varchar UNIQUE | Hostname device | |
||||
| `machine_id` | varchar | OS machine-id (tidak lagi UNIQUE sejak migration 003) | |
||||
| `smbios_uuid` | varchar | BIOS UUID | |
||||
| `serial_number` | varchar | Serial number hardware | |
||||
| `primary_physical_mac` | varchar | MAC address utama (untuk identity fallback) | |
||||
| `zabbix_host_id` | varchar nullable | ID host di Zabbix | |
||||
| `location_id` | UUID FK → locations | Lokasi fisik | |
||||
| `parent_asset_id` | UUID FK → assets | Untuk VM: parent Proxmox host | |
||||
| `status` | varchar | `active`, `inactive`, `unknown` | |
||||
| `metadata` | jsonb | Field fleksibel/rare | |
||||
| `last_seen_at` | timestamptz | Terakhir laporan masuk | |
||||
| `deleted_at` | timestamptz nullable | Soft delete | |
||||
|
||||
--- |
||||
|
||||
### `asset_snapshots` |
||||
|
||||
Point-in-time snapshot dari laporan agent. |
||||
|
||||
| Kolom | Tipe | Keterangan | |
||||
|-------|------|------------| |
||||
| `id` | UUID PK | | |
||||
| `asset_id` | UUID FK → assets | | |
||||
| `payload` | jsonb | Full payload agent (system, cpu, gpu, disks, network, dll) | |
||||
| `agent_version` | varchar | Versi agent saat collect | |
||||
| `collected_at` | timestamptz | Waktu collect di endpoint | |
||||
|
||||
> Idempotent berdasarkan `(hostname, collected_at)` — laporan duplikat diabaikan. |
||||
|
||||
--- |
||||
|
||||
### `software_installations` |
||||
|
||||
Daftar software terinstall dari scan agent. |
||||
|
||||
| Kolom | Tipe | Keterangan | |
||||
|-------|------|------------| |
||||
| `id` | UUID PK | | |
||||
| `asset_id` | UUID FK | | |
||||
| `name` | varchar | Nama software | |
||||
| `version` | varchar nullable | | |
||||
| `publisher` | varchar nullable | | |
||||
| `install_date` | date nullable | | |
||||
| `collected_at` | timestamptz | | |
||||
|
||||
--- |
||||
|
||||
### `users` |
||||
|
||||
User/karyawan (dari AD atau manual entry Laravel). |
||||
|
||||
| Kolom | Tipe | |
||||
|-------|------| |
||||
| `id` | UUID PK | |
||||
| `username` | varchar UNIQUE | |
||||
| `display_name` | varchar | |
||||
| `email` | varchar nullable | |
||||
| `department` | varchar nullable | |
||||
|
||||
--- |
||||
|
||||
### `locations` |
||||
|
||||
Lokasi fisik asset. |
||||
|
||||
| Kolom | Tipe | |
||||
|-------|------| |
||||
| `id` | UUID PK | |
||||
| `name` | varchar | |
||||
| `code` | varchar | |
||||
| `address` | varchar nullable | |
||||
|
||||
--- |
||||
|
||||
### `alerts` |
||||
|
||||
Alert dari Zabbix webhook (Fase 2). |
||||
|
||||
| Kolom | Tipe | Keterangan | |
||||
|-------|------|------------| |
||||
| `id` | UUID PK | | |
||||
| `asset_id` | UUID FK nullable | | |
||||
| `zabbix_event_id` | varchar UNIQUE | ID event Zabbix | |
||||
| `severity` | varchar | `info`, `warning`, `average`, `high`, `disaster` | |
||||
| `message` | text | | |
||||
| `status` | varchar | `problem`, `resolved` | |
||||
| `fired_at` | timestamptz | | |
||||
| `resolved_at` | timestamptz nullable | | |
||||
|
||||
--- |
||||
|
||||
### `agent_tokens` |
||||
|
||||
Token autentikasi agent (jika `AGENT_TOKEN_CHECK_DB=true`). |
||||
|
||||
| Kolom | Tipe | |
||||
|-------|------| |
||||
| `id` | UUID PK | |
||||
| `token_hash` | varchar UNIQUE | |
||||
| `description` | varchar | |
||||
| `is_active` | boolean | |
||||
| `created_at` | timestamptz | |
||||
|
||||
--- |
||||
|
||||
### `asset_invoices`, `tickets`, `document_attachments` (Fase 3 — Belum Apply) |
||||
|
||||
Draft di `infra/db/005_asset_documents.sql`. Belum dijalankan di DB. |
||||
|
||||
| Tabel | Domain | |
||||
|-------|--------| |
||||
| `asset_invoices` | Procurement / pembelian | |
||||
| `tickets` | Service request + complain | |
||||
| `document_attachments` | File di QNAP Object Storage (ADR-003) | |
||||
|
||||
--- |
||||
|
||||
## View |
||||
|
||||
### `v_asset_hardware_latest` (migration 004, dev DB) |
||||
|
||||
View yang merangkum hardware terbaru per asset dari `asset_snapshots`. |
||||
|
||||
```sql |
||||
-- Contoh query |
||||
SELECT hostname, cpu_model, ram_gb, gpu_model, os_name |
||||
FROM v_asset_hardware_latest |
||||
ORDER BY hostname; |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Migration History |
||||
|
||||
| File | Tanggal | Keterangan | |
||||
|------|---------|------------| |
||||
| `001_initial_schema.sql` | 2026-06-17 | Schema awal: assets, snapshots, software, users, licenses, alerts, agent_tokens | |
||||
| `002_disk_model.sql` | 2026-06-17 | Tambah kolom disk_model | |
||||
| `003_drop_machine_id_unique.sql` | 2026-06-22 | Hapus UNIQUE constraint machine_id (asset campur hardware) | |
||||
| `004_view_hardware_latest.sql` | 2026-06-22 | Buat view `v_asset_hardware_latest` | |
||||
| `005_asset_documents.sql` | 2026-06-22 | Draft: asset_invoices, tickets, document_attachments — **belum apply** | |
||||
|
||||
Cara apply migration: lihat `infra/db/README.md` |
||||
|
||||
--- |
||||
|
||||
## Catatan Penting |
||||
|
||||
- **Split asset:** Satu laptop bisa punya dua asset record (ADR-002). `smbios_uuid` tidak lagi UNIQUE per laptop dual-boot. |
||||
- **Snapshot flatten:** Field hardware di payload ada di root object sekarang, bukan nested `.system` lagi untuk data baru. |
||||
- **Soft delete:** Tabel master punya `deleted_at` — jangan `DELETE` langsung. |
||||
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
# Decision — Naming Convention |
||||
|
||||
> **Tanggal:** 2026-06-17 |
||||
> **Status:** Finalized |
||||
|
||||
--- |
||||
|
||||
## File & Folder |
||||
|
||||
| Tipe | Konvensi | Alasan | |
||||
|------|----------|--------| |
||||
| Python | `snake_case.py` | PEP 8 standard | |
||||
| PowerShell | `Verb-Noun.ps1` | PowerShell naming standard | |
||||
| SQL migration | `NNN_description.sql` (NNN = 3 digit) | Urutan apply yang jelas | |
||||
| Markdown doc | `kebab-case.md` | URL-friendly, konsisten | |
||||
| ADR | `ADR-NNN-title.md` | Standard ADR format | |
||||
|
||||
--- |
||||
|
||||
## Database |
||||
|
||||
| Hal | Konvensi | Contoh | |
||||
|-----|----------|--------| |
||||
| Nama tabel | `snake_case` plural | `asset_snapshots`, `software_installations` | |
||||
| Nama kolom | `snake_case` | `last_seen_at`, `smbios_uuid` | |
||||
| Primary key | selalu `id` (UUID) | `id UUID PRIMARY KEY DEFAULT gen_random_uuid()` | |
||||
| Foreign key | `<table>_id` | `asset_id`, `location_id` | |
||||
| Timestamp | `<event>_at` | `created_at`, `last_seen_at`, `collected_at` | |
||||
| Soft delete | `deleted_at` | nullable, null = aktif | |
||||
| View | `v_<description>` | `v_asset_hardware_latest` | |
||||
|
||||
--- |
||||
|
||||
## API Routes |
||||
|
||||
| Hal | Konvensi | Contoh | |
||||
|-----|----------|--------| |
||||
| Prefix | `/api/v1/` | Versioned API | |
||||
| Resource | `kebab-case` plural | `/api/v1/assets`, `/api/v1/agent-tokens` | |
||||
| Sub-resource | `/{id}/sub-resource` | `/api/v1/assets/{id}/snapshots` | |
||||
| Action route | `POST /api/v1/resource/action` | `/api/v1/agent/report` | |
||||
|
||||
--- |
||||
|
||||
## PowerShell Agent |
||||
|
||||
| Hal | Konvensi | Contoh | |
||||
|-----|----------|--------| |
||||
| Function | `Verb-Noun` | `Get-CpuInfo`, `Send-CollectorReport` | |
||||
| File | `Verb-Noun.ps1` | sama dengan function | |
||||
| Variable | `$camelCase` | `$cpuInfo`, `$reportPayload` | |
||||
| Hashtable key | `snake_case` | `@{cpu_model = ...; ram_gb = ...}` | |
||||
|
||||
--- |
||||
|
||||
## Python (FastAPI) |
||||
|
||||
| Hal | Konvensi | Contoh | |
||||
|-----|----------|--------| |
||||
| File | `snake_case.py` | `asset_service.py` | |
||||
| Class | `PascalCase` | `AssetSnapshot`, `AgentReport` | |
||||
| Function | `snake_case` | `get_asset_by_id`, `upsert_asset` | |
||||
| Variable | `snake_case` | `asset_id`, `collected_at` | |
||||
| Constant | `UPPER_SNAKE` | `DATABASE_URL` | |
||||
|
||||
--- |
||||
|
||||
## Commit Messages |
||||
|
||||
Format: `<type>: <deskripsi singkat dalam Bahasa Indonesia atau Inggris>` |
||||
|
||||
Tidak perlu body kecuali ada penjelasan penting yang tidak muat di subject. |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
# Decision — Technology Stack |
||||
|
||||
> **Tanggal:** 2026-06-17 |
||||
> **Status:** Finalized |
||||
|
||||
--- |
||||
|
||||
## Agent: PowerShell 5.1 |
||||
|
||||
**Dipilih karena:** |
||||
- Pre-installed di semua Windows 7+ — tidak perlu deploy runtime |
||||
- WMI/CIM API paling lengkap untuk hardware inventory di Windows |
||||
- Bisa berjalan sebagai SYSTEM account via Scheduled Task |
||||
- GPO support untuk deployment massal |
||||
|
||||
**Alternatif yang ditolak:** |
||||
- Python agent: perlu install Python di setiap endpoint |
||||
- C# executable: perlu .NET runtime yang spesifik, lebih kompleks untuk update |
||||
- WMI via network: perlu port WMI terbuka, security risk |
||||
|
||||
--- |
||||
|
||||
## Collector: FastAPI (Python 3.11) |
||||
|
||||
**Dipilih karena:** |
||||
- Schema validation otomatis via Pydantic (payload agent complex) |
||||
- Auto-generated OpenAPI/Swagger docs |
||||
- Async support untuk concurrent agent reports |
||||
- Developer familiar dengan Python |
||||
|
||||
**Alternatif yang ditolak:** |
||||
- Laravel saja: PHP kurang cocok untuk ingress agent yang high-frequency |
||||
- Go: learning curve lebih tinggi, tim tidak familiar |
||||
- Node.js Express: tim lebih familiar Python untuk data processing |
||||
|
||||
--- |
||||
|
||||
## Portal: Laravel (Fase 1) |
||||
|
||||
**Dipilih karena:** |
||||
- Tim familiar dengan PHP/Laravel |
||||
- Eloquent ORM cocok untuk relational data CMDB |
||||
- Laravel Sanctum untuk auth, Livewire/Blade untuk UI sederhana |
||||
- Framework yang sama dengan geonet-console (konsistensi) |
||||
|
||||
--- |
||||
|
||||
## Database: PostgreSQL 12+ |
||||
|
||||
**Dipilih karena:** |
||||
- JSONB support untuk payload agent yang flexible |
||||
- UUID native support |
||||
- PostgreSQL sudah running di infra (10.100.1.25) |
||||
- View dan CTE yang powerful untuk query CMDB |
||||
|
||||
**Alternatif yang ditolak:** |
||||
- MySQL: JSONB kurang mature di versi lama |
||||
- MongoDB: overkill untuk data yang mostly relational |
||||
- SQLite: tidak cocok untuk multi-service access |
||||
|
||||
--- |
||||
|
||||
## Object Storage: QNAP QuObjects (S3-compatible) |
||||
|
||||
**Dipilih karena:** |
||||
- On-premise — data tidak keluar jaringan internal |
||||
- S3-compatible API — bisa pakai library standard |
||||
- Infrastruktur QNAP sudah ada dan terbukti (sudah dipakai mysupperapps_FE_PWA) |
||||
- Tidak ada biaya tambahan |
||||
|
||||
--- |
||||
|
||||
## Monitoring: Zabbix (existing) |
||||
|
||||
**Dipilih karena:** |
||||
- Sudah deployed dan dikonfigurasi |
||||
- GeoNetAgent melengkapi, bukan menggantikan Zabbix |
||||
- Tidak perlu build monitoring stack baru |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
# Deployment Guide |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Deploy FastAPI Collector |
||||
|
||||
### Step 1 — Buat archive di lokal (Windows PowerShell) |
||||
|
||||
```powershell |
||||
tar -czf "$env:TEMP\collector.tar.gz" ` |
||||
--exclude=".venv" ` |
||||
--exclude="__pycache__" ` |
||||
--exclude=".pytest_cache" ` |
||||
--exclude="*.egg-info" ` |
||||
-C "D:\Project\app on git\GeoNetAgent\collector" . |
||||
``` |
||||
|
||||
### Step 2 — Upload ke server |
||||
|
||||
```powershell |
||||
scp -o "MACs=hmac-sha1" "$env:TEMP\collector.tar.gz" "root@10.100.1.24:/opt/stacks/geonetagent-collector/collector.tar.gz" |
||||
``` |
||||
|
||||
### Step 3 — Extract, rebuild, restart |
||||
|
||||
```bash |
||||
ssh -m hmac-sha1 root@10.100.1.24 -p 22 "cd /opt/stacks/geonetagent-collector && tar -xzf collector.tar.gz -C collector-src && rm collector.tar.gz && docker compose build --no-cache && docker compose up -d" |
||||
``` |
||||
|
||||
Atau gunakan script: |
||||
```powershell |
||||
.\infra\collector\push_collector.ps1 |
||||
``` |
||||
|
||||
### Step 4 — Verifikasi |
||||
|
||||
```bash |
||||
ssh -m hmac-sha1 root@10.100.1.24 -p 22 "docker ps --filter name=geonet-collector && docker logs geonet-collector --tail 20" |
||||
curl https://collector.gisportal.id/health |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Publish Agent ke Server |
||||
|
||||
Setelah bump versi agent (update `agent/VERSION` + `agent/install/manifest.json`): |
||||
|
||||
```powershell |
||||
.\infra\collector\publish_agent_install.ps1 |
||||
``` |
||||
|
||||
Script ini mengupload file agent ke `agent.gisportal.id/install/` sehingga: |
||||
- User baru mendapat versi terbaru saat install |
||||
- Agent yang sudah terpasang auto-update saat kirim laporan berikutnya (cek `manifest.json` vs `VERSION` lokal) |
||||
|
||||
--- |
||||
|
||||
## Deploy Nginx Config |
||||
|
||||
```bash |
||||
bash infra/collector/deploy_nginx.sh |
||||
``` |
||||
|
||||
Atau manual: |
||||
```powershell |
||||
scp -o "MACs=hmac-sha1" "D:\Project\app on git\GeoNetAgent\infra\nginx\collector.conf" "root@10.100.1.24:/etc/nginx/conf.d/production/collector.conf" |
||||
ssh -m hmac-sha1 root@10.100.1.24 -p 22 "nginx -t && nginx -s reload" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Apply DB Migration |
||||
|
||||
```powershell |
||||
# Upload migration file |
||||
scp -o "MACs=hmac-sha1" "D:\Project\app on git\GeoNetAgent\infra\db\00X_migration.sql" "root@10.100.1.25:/tmp/migration.sql" |
||||
|
||||
# Apply di server DB |
||||
ssh -m hmac-sha1 root@10.100.1.25 -p 22 "psql -U geonet_collector -d geonetagent_dev -f /tmp/migration.sql" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Install Agent ke Endpoint (User) |
||||
|
||||
User menjalankan di PowerShell Admin: |
||||
```powershell |
||||
Invoke-WebRequest -Uri 'https://agent.gisportal.id/install/GeoNetAgent-Install.ps1' -OutFile "$env:TEMP\GeoNetAgent-Install.ps1" -UseBasicParsing |
||||
& "$env:TEMP\GeoNetAgent-Install.ps1" |
||||
``` |
||||
|
||||
Atau dengan `-SkipInitialRun` (install task saja, tanpa kirim laporan langsung): |
||||
```powershell |
||||
& "$env:TEMP\GeoNetAgent-Install.ps1" -SkipInitialRun |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Rollout Massal via GPO |
||||
|
||||
Lihat `agent/gpo/README.md` untuk panduan script startup GPO. |
||||
|
||||
--- |
||||
|
||||
## Checklist Deployment |
||||
|
||||
- [ ] Update `agent/VERSION` (jika bump versi agent) |
||||
- [ ] Update `agent/install/manifest.json` (file list + versi) |
||||
- [ ] `publish_agent_install.ps1` — setelah bump versi agent |
||||
- [ ] `push_collector.ps1` — setelah perubahan collector/UI |
||||
- [ ] Verifikasi `docker ps` — container running |
||||
- [ ] Verifikasi `https://collector.gisportal.id/health` — 200 OK |
||||
- [ ] Verifikasi `https://agent.gisportal.id/install/` — accessible |
||||
- [ ] Update `Riwayat milestone` di `.cursor/progress.md` |
||||
- [ ] Update `Sync terakhir` di `perintah.md` |
||||
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
# Development Guide |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Setup Lokal — FastAPI Collector |
||||
|
||||
### Prerequisites |
||||
|
||||
- Python 3.11+ |
||||
- PostgreSQL 12+ (atau koneksi ke 10.100.1.25) |
||||
|
||||
### Install & Run |
||||
|
||||
```bash |
||||
cd GeoNetAgent/collector |
||||
python -m venv .venv |
||||
.\.venv\Scripts\activate # Windows |
||||
# atau: source .venv/bin/activate # Linux/Mac |
||||
|
||||
pip install -e ".[dev]" |
||||
|
||||
cp ../.env.example .env |
||||
# Edit .env: isi DATABASE_URL dan AGENT_BEARER_TOKEN |
||||
|
||||
uvicorn app.main:app --reload --port 8000 |
||||
``` |
||||
|
||||
API tersedia di `http://localhost:8000` |
||||
Swagger UI: `http://localhost:8000/docs` |
||||
UI CMDB: `http://localhost:8000/ui/` |
||||
|
||||
--- |
||||
|
||||
## Setup Lokal — PowerShell Agent |
||||
|
||||
Agent berjalan di endpoint Windows. Untuk pengembangan: |
||||
|
||||
```powershell |
||||
# Jalankan collect lokal (tanpa kirim ke server) |
||||
cd GeoNetAgent\agent |
||||
.\Collect-GeoNetReport.ps1 |
||||
|
||||
# Kirim laporan ke server dev |
||||
.\Send-GeoNetReport.ps1 -CollectorUrl "http://localhost:8000/api/v1/agent/report" -Token "dev-agent-token-change-me" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Struktur Kode Collector |
||||
|
||||
``` |
||||
collector/ |
||||
├── app/ |
||||
│ ├── main.py — FastAPI app init, lifespan, mount UI |
||||
│ ├── config.py — Settings dari .env (pydantic-settings) |
||||
│ ├── api/ — Route handlers |
||||
│ │ ├── agent.py — POST /api/v1/agent/report |
||||
│ │ ├── assets.py — GET /api/v1/assets (read API) |
||||
│ │ └── health.py — GET /health |
||||
│ ├── schemas/ — Pydantic schemas (request/response) |
||||
│ ├── services/ — Business logic |
||||
│ ├── db/ — DB session, models |
||||
│ └── core/ — Middleware, dependencies |
||||
├── static/ — UI files (HTML/CSS/JS) untuk /ui/ |
||||
└── tests/ — pytest tests |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Struktur Kode Agent |
||||
|
||||
``` |
||||
agent/ |
||||
├── Collect-GeoNetReport.ps1 — Entry point collect (gather payload) |
||||
├── Send-GeoNetReport.ps1 — Entry point kirim laporan |
||||
├── UserAgent.ps1 — Script user: cek VERSION + kirim laporan |
||||
├── Install-GeoNetAgent.ps1 — Installer (setup files + scheduled task) |
||||
├── lib/ |
||||
│ ├── Get-SystemInfo.ps1 — OS, manufacturer, model, BIOS |
||||
│ ├── Get-CpuInfo.ps1 — CPU cores, clock, cache, arch |
||||
│ ├── Get-MemoryInfo.ps1 — RAM detail, DDR type, part no |
||||
│ ├── Get-GpuInfo.ps1 — GPU dedicated memory |
||||
│ ├── Get-DiskInfo.ps1 — Logical disk info |
||||
│ ├── Get-PhysicalDiskInfo.ps1 — Physical disk model, serial |
||||
│ ├── Get-NetworkInfo.ps1 — NIC, MAC, IP, is_built_in |
||||
│ ├── Get-PeripheralsInfo.ps1 — USB/BT/audio/monitor, is_external |
||||
│ ├── Get-DisplaySettings.ps1 — Monitor EDID, resolusi |
||||
│ ├── Get-HealthInfo.ps1 — Disk usage, battery |
||||
│ ├── Get-SoftwareInfo.ps1 — Installed software list |
||||
│ ├── Get-AssetIdentity.ps1 — Resolve agent_id (ADR-001) |
||||
│ ├── Build-GeoNetReportPayload.ps1 — Assemble full JSON payload |
||||
│ ├── Agent-InstallCore.ps1 — Instalasi file + task |
||||
│ ├── Update-GeoNetAgent.ps1 — Auto-update dari manifest server |
||||
│ └── Send-CollectorReport.ps1 — HTTP POST ke collector |
||||
└── gpo/ — GPO startup scripts |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Coding Standard |
||||
|
||||
### Python (FastAPI) |
||||
- `snake_case` untuk variabel, fungsi, file |
||||
- Type hints wajib di semua function signature |
||||
- Pydantic v2 untuk schema validasi |
||||
- Route handler tipis — logic di `services/` |
||||
- Gunakan `Depends(get_db)` untuk DB session |
||||
|
||||
### PowerShell (Agent) |
||||
- Satu file per sensor di `lib/` |
||||
- Fungsi nama: `Get-<Sensor>Info` → return hashtable |
||||
- Jangan hardcode token/URL — baca dari `config.json` |
||||
- Jangan gunakan `$env:` literal di file yang di-serve Nginx |
||||
|
||||
### SQL |
||||
- PK: UUID (`gen_random_uuid()`) |
||||
- Timestamp: `timestamptz` |
||||
- Soft delete: kolom `deleted_at` |
||||
- Nama tabel: `snake_case` plural |
||||
|
||||
--- |
||||
|
||||
## Commit Convention |
||||
|
||||
Format: `<type>: <deskripsi>` |
||||
|
||||
| Type | Kapan | |
||||
|------|-------| |
||||
| `feat` | Fitur baru | |
||||
| `fix` | Bug fix | |
||||
| `agent` | Perubahan PowerShell agent | |
||||
| `collector` | Perubahan FastAPI collector | |
||||
| `infra` | Nginx, Docker, DB migration | |
||||
| `docs` | Dokumentasi | |
||||
| `chore` | Config, deps | |
||||
|
||||
--- |
||||
|
||||
## Testing |
||||
|
||||
```bash |
||||
# Run tests collector |
||||
cd collector |
||||
pytest tests/ -v |
||||
|
||||
# Manual test agent report |
||||
curl -X POST https://agent.gisportal.id/api/v1/agent/report \ |
||||
-H "Authorization: Bearer <token>" \ |
||||
-H "Content-Type: application/json" \ |
||||
-d @samples/agent-reports/sample-report-v0.1.10.json |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Menambah Sensor Baru ke Agent |
||||
|
||||
1. Buat `agent/lib/Get-<Sensor>Info.ps1` |
||||
2. Import di `Build-GeoNetReportPayload.ps1` |
||||
3. Tambah field ke schema Pydantic di `collector/app/schemas/` |
||||
4. Update `collector/docs/API.md` + `docs/api/service-collector.md` |
||||
5. Bump versi di `agent/VERSION` + `agent/install/manifest.json` |
||||
6. Publish: `.\infra\collector\publish_agent_install.ps1` |
||||
7. Update `docs/project-status.md` + `.cursor/progress.md` |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
sequenceDiagram |
||||
participant TASK as Windows Scheduled Task |
||||
participant PS as PowerShell Agent |
||||
participant NGX as Nginx (10.100.1.24) |
||||
participant COL as FastAPI Collector |
||||
participant PG as PostgreSQL |
||||
|
||||
TASK->>PS: Trigger setiap 3 jam (SYSTEM account) |
||||
PS->>PS: Baca config.json (URL, token) |
||||
PS->>NGX: GET /install/manifest.json |
||||
NGX-->>PS: {version, files[]} |
||||
|
||||
alt Versi server lebih baru |
||||
PS->>NGX: Download file-file terbaru |
||||
PS->>PS: Replace file lokal |
||||
Note over PS: Auto-update selesai |
||||
end |
||||
|
||||
PS->>PS: Get-SystemInfo, Get-CpuInfo, Get-GpuInfo |
||||
PS->>PS: Get-MemoryInfo, Get-DiskInfo, Get-PhysicalDiskInfo |
||||
PS->>PS: Get-NetworkInfo, Get-PeripheralsInfo |
||||
PS->>PS: Get-SoftwareInfo, Get-HealthInfo |
||||
PS->>PS: Get-AssetIdentity → resolve merge_key |
||||
PS->>PS: Build-GeoNetReportPayload → JSON |
||||
|
||||
PS->>NGX: POST /api/v1/agent/report\nAuthorization: Bearer token\nContent-Type: application/json |
||||
NGX->>COL: proxy_pass :18000 |
||||
COL->>COL: Validate schema (Pydantic) |
||||
COL->>COL: Validate bearer token |
||||
COL->>PG: UPSERT assets (merge_key) |
||||
PG-->>COL: asset.id |
||||
COL->>PG: INSERT asset_snapshots (payload) |
||||
COL->>PG: UPSERT software_installations |
||||
PG-->>COL: OK |
||||
COL-->>NGX: 200 {status: ok, asset_id} |
||||
NGX-->>PS: 200 OK |
||||
PS->>PS: Log sukses |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
graph LR |
||||
subgraph DEV["Developer (Windows Laptop)"] |
||||
REPO["GeoNetAgent Repo\nd:/Project/app on git"] |
||||
PS_SCRIPTS["PowerShell\nDeploy Scripts"] |
||||
end |
||||
|
||||
subgraph SERVER24["10.100.1.24 — App Server (CentOS 8)"] |
||||
NGX_SVC["Nginx\n:80/:443"] |
||||
DOCKER["Docker\ngeonet-collector\n:18000"] |
||||
STATIC_DIR["/var/www/agent-install/\n(static web install)"] |
||||
end |
||||
|
||||
subgraph SERVER25["10.100.1.25 — DB (Ubuntu 18.04)"] |
||||
PGSQL[("PostgreSQL 12\ngeonetagent_dev")] |
||||
end |
||||
|
||||
REPO -->|"publish_agent_install.ps1\nSCP upload"| STATIC_DIR |
||||
REPO -->|"push_collector.ps1\nSCP + docker compose"| DOCKER |
||||
REPO -->|"deploy_nginx.sh\nSCP + nginx reload"| NGX_SVC |
||||
REPO -->|"SCP migration.sql\npsql apply"| PGSQL |
||||
DOCKER --> PGSQL |
||||
NGX_SVC -->|"proxy_pass :18000"| DOCKER |
||||
NGX_SVC --> STATIC_DIR |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
graph TB |
||||
subgraph ENDPOINTS["Endpoint Windows"] |
||||
PS["PowerShell Agent\nv0.1.10-phase0\nScheduled Task 3 jam"] |
||||
end |
||||
|
||||
subgraph INTERNET["Internet / WAN"] |
||||
USER_IT["IT Admin\n(Browser)"] |
||||
end |
||||
|
||||
subgraph SERVER24["10.100.1.24 — App Server"] |
||||
NGX["Nginx\nSSL termination"] |
||||
COL["FastAPI Collector\n:18000\n/api/ + /ui/"] |
||||
STATIC["/var/www/agent-install/\nStatic files agent"] |
||||
end |
||||
|
||||
subgraph SERVER25["10.100.1.25 — DB Server"] |
||||
PG[("PostgreSQL 12\ngeonetagent_dev / geonetagent")] |
||||
end |
||||
|
||||
subgraph INFRA["Infrastruktur Existing"] |
||||
ZBX["Zabbix\n10.100.1.42"] |
||||
AD["AD / LDAP\n10.100.1.40"] |
||||
QNAP["QNAP NAS\n10.100.1.10\nObject Storage"] |
||||
PROX["Proxmox\n(VM sync Fase 3)"] |
||||
end |
||||
|
||||
PS -->|"HTTPS POST\nBearer token"| NGX |
||||
USER_IT -->|"HTTPS GET /install/"| NGX |
||||
USER_IT -->|"HTTPS /ui/"| NGX |
||||
NGX -->|"agent.gisportal.id"| COL |
||||
NGX -->|"collector.gisportal.id"| COL |
||||
NGX --> STATIC |
||||
COL --> PG |
||||
COL -.->|"Fase 3"| PROX |
||||
COL -.->|"Fase 2"| ZBX |
||||
ZBX -.->|"webhook Fase 2"| COL |
||||
|
||||
style PS fill:#4CAF50,color:#fff |
||||
style COL fill:#2196F3,color:#fff |
||||
style PG fill:#FF9800,color:#fff |
||||
style NGX fill:#607D8B,color:#fff |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
# Glossary |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
| Istilah | Definisi | |
||||
|---------|----------| |
||||
| **GeoNetAgent** | Nama proyek — portal IT + CMDB berbasis agent untuk inventaris endpoint Windows | |
||||
| **Agent** | PowerShell script yang berjalan di endpoint user (laptop/PC) via Scheduled Task setiap 3 jam | |
||||
| **Collector** | FastAPI service yang menerima laporan dari agent dan menyimpan ke PostgreSQL | |
||||
| **Portal** | Laravel UI untuk tim IT (belum scaffold — Fase 1 blocker) | |
||||
| **Snapshot** | Rekaman point-in-time data hardware/software dari laporan agent (bukan live metric) | |
||||
| **Asset** | Entitas terkelola: endpoint, server, VM, NAS, network device, dll | |
||||
| **CMDB** | Configuration Management Database — inventaris aset IT beserta relasi dan metadata | |
||||
| **ADR** | Architecture Decision Record — dokumen keputusan arsitektur permanen | |
||||
| **DoD** | Definition of Done — kriteria suatu fase dinyatakan selesai | |
||||
| **agent_id** | Identifier unik asset hasil resolusi ADR-001 (serial → smbios_uuid → MAC → machine_id → hostname) | |
||||
| **smbios_uuid** | UUID dari BIOS/SMBIOS — digunakan sebagai identitas asset utama | |
||||
| **machine_id** | OS-level machine identifier (bukan UNIQUE sejak migration 003) | |
||||
| **payload** | Data JSON yang dikirim agent ke collector: system, cpu, gpu, disks, network, peripherals, software | |
||||
| **v_asset_hardware_latest** | Database view yang merangkum hardware snapshot terbaru per asset | |
||||
| **manifest.json** | File di server listing semua file agent + versi — dipakai untuk auto-update | |
||||
| **auto-update** | Mekanisme agent mengunduh ulang file terbaru dari server sebelum kirim laporan (cek VERSION vs manifest) | |
||||
| **GPO** | Group Policy Object — mekanisme Windows untuk deploy script ke semua endpoint domain | |
||||
| **Web install** | Cara install agent via 2 baris PowerShell dari URL `agent.gisportal.id/install/` | |
||||
| **SSI** | Server-Side Includes — fitur Nginx yang memotong `$env:` di file `.ps1` (wajib `ssi off`) | |
||||
| **Zabbix** | Platform monitoring existing — GeoNetAgent melengkapi, bukan menggantikan | |
||||
| **Proxmox** | Platform virtualisasi — VM-nya akan di-sync ke CMDB di Fase 3 | |
||||
| **QNAP OSS** | QNAP QuObjects Object Storage — penyimpanan dokumen procurement/service/complain (ADR-003) | |
||||
| **Split asset** | ADR-002: satu chassis dengan hardware campur (laptop Dell + PC Gigabyte) dipecah jadi 2 asset record | |
||||
| **form_factor** | Field yang menentukan Laptop/PC/Desktop berdasarkan chassis type dari WMI | |
||||
| **physical_disks** | Array disk fisik (NVMe/HDD/SSD) — berbeda dari `disks` yang berisi logical partition | |
||||
| **is_external** | Flag peripheral yang menandakan apakah perangkat eksternal (USB) atau built-in | |
||||
| **is_built_in** | Flag NIC yang menandakan NIC onboard vs USB/adapter | |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
# Known Issues |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## ✅ Fixed |
||||
|
||||
### [FIXED] Nginx memotong file `.ps1` di `/install/` — `$env:` hilang |
||||
|
||||
- **Dampak:** File PowerShell yang diunduh dari `agent.gisportal.id/install/` terpotong. Kode setelah `$env:` hilang → agent gagal install. |
||||
- **Penyebab:** Nginx SSI menginterpretasikan `$env:` sebagai variabel SSI → potong respons. |
||||
- **Fix:** Tambah `ssi off` di nginx config block `location /install/`. |
||||
- **Tanggal Fix:** 2026-06-22 |
||||
|
||||
--- |
||||
|
||||
### [FIXED] `machine_id` UNIQUE constraint — split asset hardware campur |
||||
|
||||
- **Dampak:** Satu laptop yang diinstall dua OS (dual-boot) atau satu chassis yang berisi dua hardware berbeda gagal masuk DB karena `machine_id` UNIQUE. |
||||
- **Penyebab:** Constraint terlalu ketat — `machine_id` bisa sama di hardware yang berbeda dalam chassis yang sama. |
||||
- **Fix:** Migration 003 — hapus UNIQUE constraint `machine_id`. Gunakan kombinasi `smbios_uuid + hostname` untuk identity. Lihat ADR-002. |
||||
- **Tanggal Fix:** 2026-06-22 |
||||
|
||||
--- |
||||
|
||||
## 🔶 Known Limitation |
||||
|
||||
### [LIMITATION] Collector port 8000 expose ke internal network |
||||
|
||||
- **Dampak:** `http://10.100.1.24:8000/ui/` bisa diakses tanpa auth dari subnet 10.x dan 192.168.x. |
||||
- **Workaround:** Hanya untuk internal LAN. UI read-only tidak mengekspos data sensitif. |
||||
- **Rencana Fix:** Tambah auth (Laravel session) di Fase 1. |
||||
|
||||
--- |
||||
|
||||
### [LIMITATION] Nama USB device sering generik |
||||
|
||||
- **Dampak:** Peripherals HID (keyboard, mouse) terdeteksi tapi nama sering `USB Input Device` bukan nama spesifik. |
||||
- **Penyebab:** WMI `Win32_PnPEntity` tidak selalu punya nama deskriptif untuk HID generic. |
||||
- **Workaround:** Tidak ada saat ini. Klasifikasi HID yang lebih baik ada di backlog (v0.1.12+). |
||||
|
||||
--- |
||||
|
||||
### [LIMITATION] Printer belum dikumpulkan agent |
||||
|
||||
- **Dampak:** Data printer tidak ada di inventory. |
||||
- **Rencana:** Tambah `Win32_Printer` / PnP PrintQueue di v0.1.11+. |
||||
|
||||
--- |
||||
|
||||
### [LIMITATION] Laravel portal belum ada |
||||
|
||||
- **Dampak:** UI hanya tersedia di FastAPI `/ui/` yang read-only tanpa auth. |
||||
- **Rencana:** Scaffold Laravel di Fase 1 (blocker untuk DoD Fase 1). |
||||
|
||||
--- |
||||
|
||||
## 🔴 Open Issues |
||||
|
||||
### [OPEN] Uji agent di laptop kedua `BKK-MINIPC` |
||||
|
||||
- **Dampak:** Belum diverifikasi agent berjalan di PC non-laptop. |
||||
- **Prioritas:** Low |
||||
- **File:** `agent/` — tidak ada perubahan kode |
||||
|
||||
--- |
||||
|
||||
## Cara Melaporkan Issue Baru |
||||
|
||||
Tambahkan entri di file ini: |
||||
|
||||
```markdown |
||||
### [OPEN] Judul Issue |
||||
- **Dampak:** Apa yang terasa salah dari perspektif user |
||||
- **Penyebab:** Root cause (jika diketahui) |
||||
- **Workaround:** Cara sementara |
||||
- **Prioritas:** High / Medium / Low |
||||
- **Ditemukan:** YYYY-MM-DD |
||||
``` |
||||
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
# Monitoring |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Stack Monitoring |
||||
|
||||
| Service | URL | Digunakan GeoNetAgent | |
||||
|---------|-----|----------------------| |
||||
| **Zabbix** | `zabbix.gisportal.id` (10.100.1.42) | 🔶 Fase 2 — webhook + host status | |
||||
| **Collector Health** | `https://collector.gisportal.id/health` | ✅ Sudah ada | |
||||
| **Uptime Kuma** | `uptime.gisportal.id` | 🔶 Belum dikonfigurasi untuk GeoNetAgent | |
||||
|
||||
--- |
||||
|
||||
## Boundary Zabbix vs GeoNetAgent |
||||
|
||||
| Fungsi | Tool | Catatan | |
||||
|--------|------|---------| |
||||
| Uptime, CPU live, RAM live | **Zabbix** | Real-time metrics — jangan duplikasi | |
||||
| Alert firing (threshold) | **Zabbix** | Trigger + media type webhook → Laravel | |
||||
| Alert history di portal | **GeoNetAgent** (Laravel) | Dari webhook Zabbix → tabel `alerts` | |
||||
| Asset inventory (hardware) | **GeoNetAgent** (FastAPI) | Point-in-time snapshot, bukan live | |
||||
| Software inventory | **GeoNetAgent** | Zabbix tidak punya fitur ini | |
||||
| Host status badge (online/offline) | **Zabbix API** via Laravel | Cache 60 detik | |
||||
|
||||
--- |
||||
|
||||
## Health Check Collector |
||||
|
||||
```bash |
||||
# HTTP |
||||
curl https://collector.gisportal.id/health |
||||
|
||||
# Expected response |
||||
{"status": "ok", "db": "connected"} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Cek Status Container |
||||
|
||||
```bash |
||||
ssh -m hmac-sha1 root@10.100.1.24 -p 22 |
||||
|
||||
docker ps --filter name=geonet-collector --format "{{.Names}} {{.Status}}" |
||||
docker logs geonet-collector --tail 50 -f |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Monitoring Agent Activity |
||||
|
||||
Cek laporan terbaru masuk ke DB: |
||||
```sql |
||||
SELECT a.hostname, s.agent_version, s.collected_at |
||||
FROM assets a |
||||
JOIN asset_snapshots s ON s.asset_id = a.id |
||||
ORDER BY s.collected_at DESC |
||||
LIMIT 20; |
||||
``` |
||||
|
||||
Cek aset yang tidak lapor > 24 jam: |
||||
```sql |
||||
SELECT hostname, last_seen_at, |
||||
EXTRACT(EPOCH FROM (NOW() - last_seen_at))/3600 AS hours_since |
||||
FROM assets |
||||
WHERE status = 'active' |
||||
AND last_seen_at < NOW() - INTERVAL '24 hours' |
||||
ORDER BY last_seen_at; |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Integrasi Zabbix (Fase 2 — Belum Implementasi) |
||||
|
||||
Rencana arsitektur: |
||||
``` |
||||
Zabbix trigger PROBLEM |
||||
→ Media type webhook → POST Laravel /webhooks/zabbix |
||||
→ Normalize → insert tabel alerts |
||||
→ Tampil di portal IT |
||||
|
||||
Laravel dashboard |
||||
→ Zabbix API host.get → status badge hijau/merah |
||||
→ Cache 60 detik |
||||
``` |
||||
|
||||
Lihat `.cursor/monitoring-architecture.md` untuk detail. |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
# Onboarding — Coding Standard |
||||
|
||||
--- |
||||
|
||||
## Python (FastAPI Collector) |
||||
|
||||
- `snake_case` untuk variabel, fungsi, file, folder |
||||
- Type hints wajib di semua function signature |
||||
- Pydantic v2 untuk schema validasi — model di `app/schemas/`, bukan inline di routes |
||||
- Route handler tipis — logic di `app/services/` |
||||
- `Depends(get_db)` untuk DB session — jangan buat session langsung di route |
||||
- Import order: stdlib → third-party → local |
||||
|
||||
```python |
||||
# Benar |
||||
async def get_assets( |
||||
db: Session = Depends(get_db), |
||||
skip: int = 0, |
||||
limit: int = 20, |
||||
) -> list[AssetListItem]: |
||||
return asset_service.list_assets(db, skip=skip, limit=limit) |
||||
|
||||
# Salah — logic di route, tanpa type hint |
||||
@router.get("/assets") |
||||
def assets(db=Depends(get_db)): |
||||
return db.query(Asset).all() |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## PowerShell (Agent) |
||||
|
||||
- Satu file per sensor di `agent/lib/` |
||||
- Nama fungsi/file: `Verb-Noun` format (`Get-CpuInfo`, `Send-CollectorReport`) |
||||
- Return hashtable dari setiap Get- function — gunakan `@{}` bukan PSObject |
||||
- Jangan hardcode URL atau token — baca dari `config.json` |
||||
- **Jangan gunakan `$env:` literal** di file `.ps1` yang akan di-serve Nginx |
||||
|
||||
```powershell |
||||
# Benar |
||||
function Get-CpuInfo { |
||||
$cpu = Get-WmiObject Win32_Processor | Select-Object -First 1 |
||||
return @{ |
||||
model = $cpu.Name.Trim() |
||||
cores = [int]$cpu.NumberOfCores |
||||
threads = [int]$cpu.NumberOfLogicalProcessors |
||||
} |
||||
} |
||||
|
||||
# Salah — jangan return object langsung |
||||
function Get-CpuInfo { |
||||
return Get-WmiObject Win32_Processor |
||||
} |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## SQL / Database |
||||
|
||||
- PK: UUID (`gen_random_uuid()`) — jangan integer auto-increment |
||||
- Timestamp: `timestamptz` — jangan `timestamp` tanpa timezone |
||||
- Nama tabel: `snake_case` plural (`asset_snapshots`, tidak `AssetSnapshot`) |
||||
- Soft delete: `deleted_at` nullable — jangan `DELETE` pada tabel master |
||||
- Setiap migration: file baru bernomor (`NNN_description.sql`) |
||||
|
||||
--- |
||||
|
||||
## Commit Convention |
||||
|
||||
``` |
||||
<type>: <deskripsi singkat> |
||||
``` |
||||
|
||||
| Type | Kapan | |
||||
|------|-------| |
||||
| `feat` | Fitur baru | |
||||
| `fix` | Bug fix | |
||||
| `agent` | Perubahan agent PowerShell | |
||||
| `collector` | Perubahan FastAPI | |
||||
| `infra` | Nginx, Docker, DB migration | |
||||
| `docs` | Dokumentasi saja | |
||||
| `chore` | Config, deps, cleanup | |
||||
|
||||
Contoh: |
||||
``` |
||||
agent: tambah Get-PrinterInfo.ps1 untuk inventory printer |
||||
collector: fix upsert conflict pada asset dengan machine_id duplikat |
||||
infra: apply migration 003 drop machine_id unique constraint |
||||
docs: update ADR-002 split asset hardware campur |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Naming Convention |
||||
|
||||
| Hal | Konvensi | Contoh | |
||||
|-----|----------|--------| |
||||
| Python file | `snake_case.py` | `asset_service.py` | |
||||
| PS1 file | `Verb-Noun.ps1` | `Get-CpuInfo.ps1` | |
||||
| SQL migration | `NNN_description.sql` | `003_drop_unique.sql` | |
||||
| DB tabel | `snake_case` plural | `asset_snapshots` | |
||||
| DB kolom | `snake_case` | `last_seen_at` | |
||||
| FastAPI route | `/api/v1/kebab-case` | `/api/v1/agent/report` | |
||||
| Doc file | `kebab-case.md` | `system-overview.md` | |
||||
| ADR | `ADR-NNN-title.md` | `ADR-001-agent-asset-identity.md` | |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
# Onboarding — Hari Pertama |
||||
|
||||
> Untuk developer baru, IT admin baru, dan AI Agent yang baru masuk ke proyek ini. |
||||
|
||||
--- |
||||
|
||||
## 1. Baca Dulu |
||||
|
||||
Urutan wajib: |
||||
1. `docs/context-index.md` — peta navigasi seluruh proyek |
||||
2. `README.md` — ringkasan + quick start |
||||
3. `.cursor/blueprint.md` — visi, fase, modul |
||||
4. `.cursor/progress.md` — status fase aktif |
||||
|
||||
--- |
||||
|
||||
## 2. Pahami Proyek |
||||
|
||||
GeoNetAgent adalah **IT Asset CMDB ringan** yang: |
||||
- Mengumpulkan data hardware/software dari endpoint Windows via **PowerShell Agent** (berjalan otomatis setiap 3 jam) |
||||
- Menyimpan ke **PostgreSQL** via **FastAPI Collector** |
||||
- Menampilkan di UI CMDB (**Collector UI** saat ini, **Laravel Portal** nanti) |
||||
|
||||
**Bukan monitoring real-time** — itu urusan Zabbix. GeoNetAgent melengkapi Zabbix dengan inventaris hardware/software. |
||||
|
||||
--- |
||||
|
||||
## 3. Struktur Monorepo |
||||
|
||||
``` |
||||
agent/ ← PowerShell scripts (install ke endpoint Windows) |
||||
collector/ ← FastAPI Python (server penerima laporan) |
||||
infra/ ← Nginx, DB migration, Docker, QNAP |
||||
docs/ ← Dokumentasi (kamu di sini!) |
||||
.cursor/ ← AI context (blueprint, schema, API contracts) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## 4. URL yang Perlu Diketahui |
||||
|
||||
| URL | Fungsi | |
||||
|-----|--------| |
||||
| `https://agent.gisportal.id/install/` | Web install agent untuk user | |
||||
| `https://agent.gisportal.id/api/v1/agent/report` | POST endpoint agent | |
||||
| `https://collector.gisportal.id/ui/` | UI CMDB (read-only) | |
||||
| `https://collector.gisportal.id/docs` | Swagger UI API | |
||||
|
||||
--- |
||||
|
||||
## 5. Fase Yang Aktif Saat Ini |
||||
|
||||
**Fase 1 — Foundation** (8 dari 9 selesai) |
||||
|
||||
Yang tersisa: Laravel asset index page + auth IT user lokal. |
||||
|
||||
Jangan mulai Fase 2 (Zabbix) sebelum Fase 1 DoD selesai. |
||||
|
||||
--- |
||||
|
||||
## 6. Cara Kerja Agent |
||||
|
||||
``` |
||||
Endpoint Windows |
||||
→ PowerShell Scheduled Task (SYSTEM, 3 jam) |
||||
→ WMI scan (CPU, RAM, GPU, disk, software, peripherals) |
||||
→ POST https://agent.gisportal.id/api/v1/agent/report |
||||
→ FastAPI → PostgreSQL |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## 7. Akses Server |
||||
|
||||
Semua server butuh **SSH key** yang sudah dikonfigurasi. Lihat `docs/server.md` untuk command lengkap. |
||||
|
||||
> SSH commands ke server **wajib minta persetujuan user** sebelum dijalankan. |
||||
|
||||
--- |
||||
|
||||
## 8. Aturan Penting |
||||
|
||||
- Jangan commit `.env` atau secret ke repo |
||||
- Jangan implementasi Fase N+1 jika Fase N belum DoD |
||||
- Setelah selesai: update `perintah.md` + `.cursor/progress.md` |
||||
- ADR tidak boleh dihapus — buat baru jika keputusan berubah |
||||
|
||||
--- |
||||
|
||||
## Lanjutkan ke |
||||
|
||||
- [`local-setup.md`](local-setup.md) — cara setup lokal untuk dev |
||||
- [`coding-standard.md`](coding-standard.md) — standar kode |
||||
- [`docs/development.md`](../development.md) — panduan pengembangan lengkap |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
# Onboarding — Local Setup |
||||
|
||||
--- |
||||
|
||||
## Prerequisites |
||||
|
||||
- Python 3.11+ |
||||
- PostgreSQL 12+ (lokal atau koneksi ke 10.100.1.25) |
||||
- PowerShell 5.1+ (Windows, untuk test agent) |
||||
- Git |
||||
|
||||
--- |
||||
|
||||
## Setup FastAPI Collector |
||||
|
||||
```bash |
||||
cd GeoNetAgent/collector |
||||
|
||||
# Buat virtual environment |
||||
python -m venv .venv |
||||
|
||||
# Aktivasi |
||||
.\.venv\Scripts\activate # Windows PowerShell |
||||
source .venv/bin/activate # Linux/Mac |
||||
|
||||
# Install dependencies |
||||
pip install -e ".[dev]" |
||||
|
||||
# Copy env template |
||||
cp ../.env.example .env |
||||
``` |
||||
|
||||
Edit `.env`: |
||||
```env |
||||
DATABASE_URL=postgresql://user:pass@localhost:5432/geonetagent_dev |
||||
AGENT_BEARER_TOKEN=dev-agent-token-change-me |
||||
CORS_ORIGINS=* |
||||
``` |
||||
|
||||
```bash |
||||
# Jalankan dev server |
||||
uvicorn app.main:app --reload --port 8000 |
||||
``` |
||||
|
||||
Buka `http://localhost:8000/docs` untuk Swagger UI. |
||||
|
||||
--- |
||||
|
||||
## Setup DB Lokal (opsional) |
||||
|
||||
Jika tidak mau koneksi ke server 10.100.1.25: |
||||
|
||||
```bash |
||||
# Buat DB lokal |
||||
createdb geonetagent_dev |
||||
|
||||
# Apply semua migration |
||||
psql geonetagent_dev -f infra/db/001_initial_schema.sql |
||||
psql geonetagent_dev -f infra/db/002_disk_model.sql |
||||
psql geonetagent_dev -f infra/db/003_drop_machine_id_unique.sql |
||||
psql geonetagent_dev -f infra/db/004_view_hardware_latest.sql |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Test Agent Lokal |
||||
|
||||
```powershell |
||||
# Kirim sample payload ke server lokal |
||||
curl -X POST http://localhost:8000/api/v1/agent/report ` |
||||
-H "Authorization: Bearer dev-agent-token-change-me" ` |
||||
-H "Content-Type: application/json" ` |
||||
-d (Get-Content "samples/agent-reports/sample-report-v0.1.10.json" -Raw) |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Verifikasi |
||||
|
||||
```bash |
||||
# Health check |
||||
curl http://localhost:8000/health |
||||
# Expected: {"status": "ok", "db": "connected"} |
||||
|
||||
# List assets (setelah ada laporan masuk) |
||||
curl -H "Authorization: Bearer dev-agent-token-change-me" \ |
||||
http://localhost:8000/api/v1/assets |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Tools Direkomendasikan |
||||
|
||||
| Tool | Fungsi | |
||||
|------|--------| |
||||
| VS Code | IDE utama | |
||||
| Thunder Client / Postman | Test API | |
||||
| pgAdmin / DBeaver | DB GUI | |
||||
| Git Graph extension | Visualisasi branch | |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
# Project Status |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Snapshot Cepat |
||||
|
||||
| Item | Status | |
||||
|------|--------| |
||||
| **Fase aktif** | Fase 1 — Foundation 🔄 | |
||||
| **Agent versi** | v0.1.10-phase0 | |
||||
| **Collector** | Running di `collector.gisportal.id` | |
||||
| **Database** | `geonetagent_dev` PostgreSQL 12 di 10.100.1.25 (dev) | |
||||
| **Portal Laravel** | ❌ Belum di-scaffold | |
||||
| **Blocker utama** | Laravel asset index page | |
||||
|
||||
--- |
||||
|
||||
## Modul yang Sudah Selesai |
||||
|
||||
| Modul | Versi/Tanggal | Keterangan | |
||||
|-------|--------------|------------| |
||||
| ✅ DB Schema migration 001 | 2026-06-17 | Initial schema: assets, snapshots, software, users, licenses | |
||||
| ✅ DB migration 002 | 2026-06-17 | disk_model kolom | |
||||
| ✅ DB migration 003 | 2026-06-22 | Drop machine_id UNIQUE constraint | |
||||
| ✅ DB migration 004 | 2026-06-22 | View `v_asset_hardware_latest` | |
||||
| ✅ FastAPI `/api/v1/agent/report` | 2026-06-17 | Ingress laporan agent, upsert assets + snapshots | |
||||
| ✅ FastAPI `/api/v1/assets` read API | 2026-06-17 | List, detail, filter | |
||||
| ✅ FastAPI `/health` | 2026-06-17 | Health check endpoint | |
||||
| ✅ FastAPI UI `/ui/` | 2026-06-22 | Read-only UI untuk CMDB data | |
||||
| ✅ Agent v0.1.6 | 2026-06-17 | Peripherals USB/BT/audio, scheduled task 3 jam | |
||||
| ✅ Agent v0.1.8 | 2026-06-22 | gpus[], disk model, auto-update, fix nginx $env: | |
||||
| ✅ Agent v0.1.9 | 2026-06-22 | physical_disks[], RAM DDR, GPU detail, form_factor | |
||||
| ✅ Agent v0.1.10 | 2026-06-22 | cpu{} detail (cores, clock, cache, arch), deploy PS1 | |
||||
| ✅ Web install | 2026-06-17 | `agent.gisportal.id/install/` + auto-update via manifest | |
||||
| ✅ Nginx reverse proxy | 2026-06-17 | agent.gisportal.id + collector.gisportal.id | |
||||
| ✅ Asset identity merge | 2026-06-17 | ADR-001: serial → smbios_uuid → MAC → machine_id | |
||||
| ✅ Split asset hardware campur | 2026-06-22 | ADR-002: pilot DESKTOP-RRJ9G01 laptop+PC | |
||||
| ✅ GPO scripts | 2026-06-22 | `agent/gpo/` — siap deploy, belum rollout ke semua | |
||||
| ✅ ADR-003 draft | 2026-06-22 | Desain dokumen QNAP + migration 005 draft SQL | |
||||
|
||||
--- |
||||
|
||||
## Modul Sedang Dikerjakan |
||||
|
||||
Tidak ada yang sedang in-progress saat ini. |
||||
|
||||
--- |
||||
|
||||
## Modul Belum Dibuat (Backlog) |
||||
|
||||
| Fase | Modul | Keterangan | |
||||
|------|-------|------------| |
||||
| Fase 1 | **Laravel asset index** | Halaman list asset read-only — BLOCKER | |
||||
| Fase 1 | **Auth IT user** | Login lokal untuk portal Laravel (M9) | |
||||
| Fase 2 | Zabbix webhook → Laravel | Alert Zabbix di portal | |
||||
| Fase 2 | Zabbix host status cache | Status hijau/merah dari Zabbix API | |
||||
| Fase 3 | Proxmox sync job (FastAPI) | VM otomatis masuk CMDB | |
||||
| Fase 3 | License module (Laravel) | Entri manual lisensi software | |
||||
| Fase 3 | User ↔ device assignment UI | Assign user ke asset | |
||||
| Fase 3 | Migration 005 + QNAP bucket | `asset_invoices`, `tickets`, `document_attachments` | |
||||
| Fase 3 | Laravel dokumen upload | Invoice, service report, complain | |
||||
| Fase 4 | GPO rollout ke semua laptop | Web install sudah siap | |
||||
| Fase 4 | Trend & compliance report | Analisis dari asset_snapshots | |
||||
| Fase 4 | Ollama assistant (opsional) | Query infra dengan natural language | |
||||
|
||||
--- |
||||
|
||||
## Technical Debt |
||||
|
||||
| Item | File | Prioritas | Keterangan | |
||||
|------|------|-----------|------------| |
||||
| ADR di `.cursor/adr/` tapi folder kosong | `.cursor/adr/` | Medium | ADR sebenarnya belum dibuat sebagai file — hanya disebut di progress.md | |
||||
| Collector port 8000 expose internal | Nginx | Low | Port 8000 di-allow firewalld untuk subnet 10.x, 192.168.x | |
||||
| `portal/` belum scaffold | monorepo | High | Laravel portal belum ada sama sekali | |
||||
| Uji agent di `BKK-MINIPC` | - | Low | Laptop kedua belum diuji | |
||||
|
||||
--- |
||||
|
||||
## Agent Backlog (Fitur Belum Diimplementasi) |
||||
|
||||
| Target Versi | Fitur | |
||||
|-------------|-------| |
||||
| v0.1.11+ | Printer (`Win32_Printer` / PnP PrintQueue) | |
||||
| v0.1.12+ | Klasifikasi HID keyboard vs mouse (saat ini generik) | |
||||
| Fase 3 | `is_factory` / baseline hardware vs upgrade aftermarket | |
||||
|
||||
--- |
||||
|
||||
## Known Issues |
||||
|
||||
Lihat [`issues.md`](issues.md) untuk detail lengkap. |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
# Roadmap |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Status Saat Ini |
||||
|
||||
``` |
||||
✅ Fase 0 — Blueprint & Scaffold — Selesai Jun 2026 |
||||
🔄 Fase 1 — Foundation (8/9) — Aktif, blocker: Laravel asset index |
||||
⏳ Fase 2 — Zabbix Integrasi — Pending |
||||
⏳ Fase 3 — CMDB Lengkap + Dokumen — Pending |
||||
⏳ Fase 4 — Scale & Intelligence — Pending |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Fase 1 — Foundation 🔄 (Aktif) |
||||
|
||||
**DoD:** Agent di 1 laptop test POST → data di DB → Laravel tampilkan halaman asset list. |
||||
|
||||
Yang sudah selesai: |
||||
- ✅ DB Schema migrated (geonetagent_dev) |
||||
- ✅ FastAPI `/agent/report` + health + read API + UI `/ui/` |
||||
- ✅ PowerShell agent v0.1.10 + POST ke collector (E2E) |
||||
- ✅ Nginx reverse proxy + web install `agent.gisportal.id/install/` |
||||
- ✅ Asset identity merge (ADR-001) + split hardware (ADR-002) |
||||
|
||||
**Yang tersisa (blocker):** |
||||
- ❌ **Laravel asset index page** (read-only) — M4 |
||||
- ❌ **Auth IT user lokal** — M9 |
||||
|
||||
--- |
||||
|
||||
## Fase 2 — Integrasi Monitoring |
||||
|
||||
**DoD:** Alert Zabbix muncul di portal; status host dari Zabbix API ditampilkan. |
||||
|
||||
- [ ] Zabbix webhook → Laravel (`POST /webhooks/zabbix`) |
||||
- [ ] Zabbix host status cache di dashboard (via Zabbix API) |
||||
|
||||
--- |
||||
|
||||
## Fase 3 — CMDB Lengkap + Dokumen |
||||
|
||||
**DoD:** VM Proxmox otomatis di CMDB; license dan dokumen asset bisa di-assign. |
||||
|
||||
- [ ] Proxmox sync job (FastAPI APScheduler) |
||||
- [ ] License module (Laravel CRUD) |
||||
- [ ] User ↔ device assignment UI |
||||
- [ ] Migration 005 + bucket `geonetagent-documents` di QNAP |
||||
- [ ] Laravel upload/download dokumen (invoice, service report, complain) |
||||
|
||||
--- |
||||
|
||||
## Fase 4 — Scale & Intelligence |
||||
|
||||
- [ ] GPO rollout ke semua endpoint (web install sudah siap) |
||||
- [ ] Trend disk usage dari `asset_snapshots` |
||||
- [ ] Compliance report PDF |
||||
- [ ] Ollama assistant (opsional — query infra dengan natural language) |
||||
|
||||
--- |
||||
|
||||
## Agent Backlog |
||||
|
||||
| Target | Fitur | |
||||
|--------|-------| |
||||
| v0.1.11+ | Printer (`Win32_Printer`) | |
||||
| v0.1.12+ | Klasifikasi HID keyboard vs mouse | |
||||
| Fase 3 | `is_factory` / baseline vs upgrade hardware | |
||||
|
||||
--- |
||||
|
||||
## Lihat Juga |
||||
|
||||
- `docs/project-status.md` — status modul detail |
||||
- `docs/todo.md` — next actions berprioritas |
||||
- `.cursor/progress.md` — checklist DoD per fase |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
# Security |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Credential Management |
||||
|
||||
| Secret | Lokasi | TIDAK boleh di | |
||||
|--------|--------|----------------| |
||||
| DB password | `/root/geonetagent_dev.credentials` di server 10.100.1.25 | Repo, log, chat | |
||||
| Agent bearer token | `.env` di server collector (10.100.1.24) | Repo | |
||||
| Zabbix API token | `.env` Laravel (Fase 2) | Repo | |
||||
| QNAP OSS credentials | `infra/qnap/README.md` (referensi lokasi) | Repo | |
||||
| SSH private keys | `~/.ssh/` di laptop developer | Repo, cloud | |
||||
|
||||
--- |
||||
|
||||
## Auth Model |
||||
|
||||
### Agent → Collector (FastAPI) |
||||
- HTTPS + `Authorization: Bearer <agent_token>` |
||||
- Token statis per site (`config.json` di endpoint) |
||||
- Opsi: validasi token ke DB jika `AGENT_TOKEN_CHECK_DB=true` |
||||
- Produksi: tambah mTLS (rencana) |
||||
|
||||
### User → Collector UI |
||||
- Saat ini: **tidak ada auth** — UI read-only, hanya internal LAN |
||||
- Fase 1: Laravel auth (session / Sanctum) |
||||
|
||||
### Zabbix Webhook (Fase 2) |
||||
- Shared secret di header |
||||
- IP allowlist Zabbix server |
||||
|
||||
--- |
||||
|
||||
## Aturan AI Agent |
||||
|
||||
1. **Jangan commit secret** ke repository |
||||
2. **Jangan tampilkan nilai secret** di output percakapan |
||||
3. Jangan hardcode credentials di kode |
||||
4. Credentials update di server → via SSH langsung, bukan melalui kode yang di-commit |
||||
5. SSH command ke server **wajib minta persetujuan user** sebelum dijalankan |
||||
|
||||
--- |
||||
|
||||
## Nginx Security Notes |
||||
|
||||
- `agent.gisportal.id`: `ssi off` — **wajib** agar file `.ps1` tidak terpotong |
||||
- Jangan taruh `$env:` literal di file `.ps1` yang di-serve Nginx |
||||
- HTTPS via Let's Encrypt (certbot) di 10.100.1.24 |
||||
- Internal UI (`/ui/`) hanya diakses via HTTPS domain, tidak direct IP:port (kecuali internal LAN) |
||||
|
||||
--- |
||||
|
||||
## Data Sensitivity |
||||
|
||||
| Data | Sensitivitas | Catatan | |
||||
|------|-------------|---------| |
||||
| Hostname, domain username | Medium | PII — diaudit akses | |
||||
| Hardware spec (CPU, RAM, GPU) | Low | Data inventaris | |
||||
| Software list | Medium | Bisa digunakan untuk targeting | |
||||
| IP/MAC address | Medium | Network fingerprinting | |
||||
| Serial number | Medium | Asset tracking | |
||||
| Credentials DB | **Critical** | Tidak boleh ada di kode | |
||||
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
# Server Information |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Daftar Server |
||||
|
||||
| Role | IP | OS | SSH Command | |
||||
|------|----|----|-------------| |
||||
| **Reverse Proxy + Collector** | 10.100.1.24 | CentOS Linux 8 | `ssh -m hmac-sha1 root@10.100.1.24 -p 22` | |
||||
| **PostgreSQL** | 10.100.1.25 | Ubuntu 18.04.6 LTS | `ssh -m hmac-sha1 root@10.100.1.25 -p 22` | |
||||
| **AD / LDAP** | 10.100.1.40 | Windows Server 2012 R2 | `ssh -m hmac-sha1 Administrator@10.100.1.40 -p 22` | |
||||
| **Main Webserver** | 10.100.1.41 | Windows Server 2012 R2 | `ssh -m hmac-sha1 Administrator@10.100.1.41 -p 22` | |
||||
| **Zabbix** | 10.100.1.42 | Ubuntu 22.04.1 LTS | `ssh -m hmac-sha1 root@10.100.1.42 -p 22` | |
||||
| **Ollama** | 10.100.1.26 | Ubuntu 22.04.1 LTS | `ssh -i "$env:USERPROFILE\.ssh\id_rsa_geonet_laptop" -o IdentitiesOnly=yes -m hmac-sha1 root@10.100.1.26 -p 22` | |
||||
| **NAS QNAP TS-932X** | 10.100.1.10 | QTS 5.2.9 (ARM64) | `ssh -i "$env:USERPROFILE\.ssh\id_ed25519" -o IdentitiesOnly=yes admin@10.100.1.10 -p 22` | |
||||
| **MikroTik CCR2004** | 10.100.1.1 / 192.168.0.1 | RouterOS | `ssh -i "$env:USERPROFILE\.ssh\id_rsa_mikrotik" -o IdentitiesOnly=yes -m hmac-sha1 -p 255 admin@10.100.1.1` | |
||||
|
||||
> Ollama & QNAP: Wajib **PowerShell** (bukan CMD) agar `$env:USERPROFILE` terbaca. |
||||
> SSH key sudah dikonfigurasi. Semua SSH command butuh **approval user** sebelum dijalankan. |
||||
|
||||
--- |
||||
|
||||
## Server 10.100.1.24 — Reverse Proxy + Collector |
||||
|
||||
### Services Running |
||||
|
||||
| Service | Container | Port | URL | |
||||
|---------|-----------|------|-----| |
||||
| FastAPI Collector | `geonet-collector` | 18000 (internal: 8000) | `collector.gisportal.id` | |
||||
| Nginx | host | 80, 443 | reverse proxy | |
||||
|
||||
### Nginx Config Files |
||||
|
||||
| File | Domain | |
||||
|------|--------| |
||||
| `/etc/nginx/conf.d/production/collector.conf` | `collector.gisportal.id`, `agent.gisportal.id` | |
||||
|
||||
### Perintah Umum |
||||
|
||||
```bash |
||||
# Cek container |
||||
docker ps |
||||
|
||||
# Log collector |
||||
docker logs geonet-collector --tail 50 -f |
||||
|
||||
# Reload nginx |
||||
nginx -t && nginx -s reload |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Server 10.100.1.25 — PostgreSQL |
||||
|
||||
```bash |
||||
# Akses PostgreSQL |
||||
ssh -m hmac-sha1 root@10.100.1.25 -p 22 |
||||
# Credentials: /root/geonetagent_dev.credentials |
||||
|
||||
psql -U geonet_collector -d geonetagent_dev |
||||
|
||||
# Migration manual |
||||
psql -U geonet_collector -d geonetagent_dev -f infra/db/migration.sql |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Domain Map |
||||
|
||||
| Domain | Backend | |
||||
|--------|---------| |
||||
| `agent.gisportal.id` | 10.100.1.24 — collector :18000 (POST agent) + `/install/` (static) | |
||||
| `collector.gisportal.id` | 10.100.1.24 — collector :18000 (read UI + API) | |
||||
|
||||
--- |
||||
|
||||
## Transfer File ke Server |
||||
|
||||
```powershell |
||||
# Upload file (Windows PowerShell) |
||||
scp -o "MACs=hmac-sha1" "D:\path\to\file" "root@10.100.1.24:/path/on/server" |
||||
|
||||
# Upload archive (source code) |
||||
tar -czf "$env:TEMP\collector.tar.gz" --exclude=".venv" --exclude="__pycache__" -C "D:\Project\app on git\GeoNetAgent\collector" . |
||||
scp -o "MACs=hmac-sha1" "$env:TEMP\collector.tar.gz" "root@10.100.1.24:/opt/stacks/geonetagent-collector/collector.tar.gz" |
||||
``` |
||||
|
||||
> **Jangan** gunakan heredoc `<< 'EOF'` via SSH dari PowerShell — `$` variabel terpotong. |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
# TODO List |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## 🔴 High Priority (Fase 1 Blocker) |
||||
|
||||
### Scaffold Laravel Portal — Asset Index Page |
||||
- **Deskripsi:** Buat Laravel project di `portal/`, halaman list asset read-only dari DB `geonetagent` |
||||
- **File:** `portal/` (belum ada), `infra/db/` (credentials) |
||||
- **Estimasi:** 2-3 hari |
||||
- **Dependency:** DB sudah running, FastAPI data sudah ada |
||||
|
||||
### Auth IT User — Login Lokal Laravel |
||||
- **Deskripsi:** M9: Login lokal (bukan LDAP dulu) untuk IT admin mengakses portal |
||||
- **File:** `portal/` Laravel (M9) |
||||
- **Estimasi:** 1 hari |
||||
- **Dependency:** Laravel scaffold selesai |
||||
|
||||
--- |
||||
|
||||
## 🟡 Medium Priority |
||||
|
||||
### Buat ADR files di docs/adr/ |
||||
- **Deskripsi:** ADR-001, ADR-002, ADR-003 belum ada sebagai file — hanya disebut di progress.md dan context-index. Perlu dibuat sebagai file `.md` di `docs/adr/` |
||||
- **File:** `docs/adr/ADR-001-*.md`, `docs/adr/ADR-002-*.md`, `docs/adr/ADR-003-*.md` |
||||
- **Estimasi:** 1 jam |
||||
- **Dependency:** - |
||||
|
||||
### Isi .cursor/rules/ files |
||||
- **Deskripsi:** Folder `.cursor/rules/` ada tapi kosong (hanya `project-context.mdc` yang disebut tapi tidak ada) |
||||
- **File:** `.cursor/rules/project-context.mdc`, `coding.mdc`, `agent-release-deploy.mdc` |
||||
- **Estimasi:** 1 jam |
||||
- **Dependency:** - |
||||
|
||||
### Apply migration 005 di dev DB |
||||
- **Deskripsi:** `infra/db/005_asset_documents.sql` sudah dibuat, belum diapply. Butuh setup QNAP bucket dulu. |
||||
- **File:** `infra/db/005_asset_documents.sql` |
||||
- **Estimasi:** 2 jam |
||||
- **Dependency:** QNAP bucket `geonetagent-documents` dibuat |
||||
|
||||
### Buat QNAP bucket geonetagent-documents |
||||
- **Deskripsi:** Bucket untuk dokumen procurement/service/complain (ADR-003) |
||||
- **File:** `infra/qnap/README.md` (referensi) |
||||
- **Estimasi:** 30 menit |
||||
- **Dependency:** ADR-003 |
||||
|
||||
### Uji agent di BKK-MINIPC |
||||
- **Deskripsi:** Verifikasi agent berjalan di PC kedua (bukan laptop Dell) |
||||
- **Estimasi:** 30 menit |
||||
- **Dependency:** Akses fisik ke BKK-MINIPC |
||||
|
||||
--- |
||||
|
||||
## 🟢 Low Priority |
||||
|
||||
### Agent v0.1.11 — Printer collection |
||||
- **Deskripsi:** Tambah `Get-PrinterInfo.ps1` menggunakan `Win32_Printer` |
||||
- **File:** `agent/lib/Get-PrinterInfo.ps1` (baru) |
||||
- **Estimasi:** 2-3 jam |
||||
- **Dependency:** - |
||||
|
||||
### Agent v0.1.12 — Klasifikasi HID |
||||
- **Deskripsi:** Bedakan keyboard vs mouse vs dongle di `Get-PeripheralsInfo.ps1` |
||||
- **File:** `agent/lib/Get-PeripheralsInfo.ps1` |
||||
- **Estimasi:** 2-4 jam |
||||
- **Dependency:** - |
||||
|
||||
### Tambah pytest tests untuk collector |
||||
- **Deskripsi:** Coverage untuk `/api/v1/agent/report` endpoint (test payload validation, upsert logic) |
||||
- **File:** `collector/tests/` |
||||
- **Estimasi:** 1-2 hari |
||||
- **Dependency:** - |
||||
|
||||
### OpenAPI / Swagger doc finalize |
||||
- **Deskripsi:** Pastikan semua endpoint FastAPI punya docstring dan description yang lengkap untuk auto-generated docs di `/docs` |
||||
- **File:** `collector/app/api/*.py` |
||||
- **Estimasi:** 2 jam |
||||
- **Dependency:** - |
||||
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
# Troubleshooting |
||||
|
||||
> **Terakhir Diperbarui:** 2026-06-29 |
||||
|
||||
--- |
||||
|
||||
## Agent gagal kirim laporan — "Cannot connect" |
||||
|
||||
**Kemungkinan:** |
||||
1. URL di `config.json` salah |
||||
2. Token tidak cocok dengan `AGENT_BEARER_TOKEN` di collector |
||||
|
||||
```powershell |
||||
# Cek config |
||||
Get-Content "$env:ProgramData\GeoNetAgent\config.json" |
||||
|
||||
# Test manual (dari endpoint) |
||||
& "$env:ProgramData\GeoNetAgent\UserAgent.ps1" |
||||
|
||||
# Cek koneksi ke server |
||||
Test-NetConnection agent.gisportal.id -Port 443 |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## File `.ps1` di Nginx terpotong — `$env:` hilang |
||||
|
||||
**Penyebab:** Nginx menginterpretasikan `$env:` sebagai variabel Nginx SSI dan memotong respons. |
||||
|
||||
**Fix:** Pastikan nginx config untuk `agent.gisportal.id` memiliki `ssi off`: |
||||
```nginx |
||||
location /install/ { |
||||
ssi off; |
||||
alias /var/www/agent-install/; |
||||
} |
||||
``` |
||||
|
||||
**Cara deteksi:** Download file `.ps1` dari URL, bandingkan dengan source lokal. Jika ada teks yang hilang setelah `$env:`, ini masalahnya. |
||||
|
||||
--- |
||||
|
||||
## Heredoc via SSH PowerShell — Variable $ hilang |
||||
|
||||
**Penyebab:** PowerShell menginterpretasikan `$` sebelum dikirim ke SSH. |
||||
|
||||
**Solusi:** Upload file via SCP: |
||||
```powershell |
||||
scp -o "MACs=hmac-sha1" "D:\path\to\file.sh" "root@10.100.1.24:/path/on/server/file.sh" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## SCP error "unknown option -- m" |
||||
|
||||
Windows SCP tidak support flag `-m`. Gunakan `-o "MACs=hmac-sha1"`: |
||||
```powershell |
||||
scp -o "MACs=hmac-sha1" source destination |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Agent auto-update tidak berjalan |
||||
|
||||
**Cek:** |
||||
```powershell |
||||
# Cek versi lokal |
||||
Get-Content "$env:ProgramData\GeoNetAgent\VERSION" |
||||
|
||||
# Cek manifest server (harus accessible) |
||||
Invoke-WebRequest "https://agent.gisportal.id/install/manifest.json" -UseBasicParsing | Select-Object -ExpandProperty Content |
||||
``` |
||||
|
||||
**Auto-update hanya berjalan saat `UserAgent.ps1` dieksekusi** (setiap 3 jam via Scheduled Task). Jika task tidak running: |
||||
```powershell |
||||
Get-ScheduledTask -TaskName "GeoNetAgent-Report" | Select State |
||||
Start-ScheduledTask -TaskName "GeoNetAgent-Report" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Collector container tidak mau start |
||||
|
||||
```bash |
||||
# Cek exit code |
||||
docker ps -a --filter name=geonet-collector |
||||
|
||||
# Cek log |
||||
docker logs geonet-collector --tail 100 |
||||
|
||||
# Cek DATABASE_URL valid |
||||
docker exec geonet-collector python -c "from app.config import settings; print(settings.database_url)" |
||||
|
||||
# Test koneksi DB dari container |
||||
docker exec geonet-collector psql "$DATABASE_URL" -c "SELECT 1" |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Asset duplikat muncul setelah split hardware |
||||
|
||||
**Penyebab:** Laptop dengan dual hardware (misalnya laptop Dell + PC Gigabyte dalam satu unit) — sesuai ADR-002. |
||||
|
||||
**Cara split:** |
||||
```bash |
||||
docker exec geonet-collector python /app/scripts/split_asset_by_hardware.py <uuid> |
||||
``` |
||||
|
||||
Lihat `ADR-002` untuk detail keputusan. |
||||
|
||||
--- |
||||
|
||||
## View `v_asset_hardware_latest` tidak ada di DB |
||||
|
||||
Migration 004 mungkin belum diapply di environment ini. |
||||
|
||||
```bash |
||||
ssh -m hmac-sha1 root@10.100.1.25 -p 22 |
||||
psql -U geonet_collector -d geonetagent_dev -c "\dv" |
||||
# Jika tidak ada v_asset_hardware_latest: |
||||
psql -U geonet_collector -d geonetagent_dev -f /tmp/004_view_hardware_latest.sql |
||||
``` |
||||
|
||||
--- |
||||
|
||||
## Perintah Darurat |
||||
|
||||
```bash |
||||
# Restart collector |
||||
docker restart geonet-collector |
||||
|
||||
# Nginx reload |
||||
nginx -t && nginx -s reload |
||||
|
||||
# Cek semua container |
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" |
||||
``` |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
# Publish agent install package ke proxy.lan (nginx /install/) |
||||
# Jalankan dari root repo (PowerShell): |
||||
# .\infra\collector\publish_agent_install.ps1 |
||||
# Opsional: -Remote reverse-proxy |
||||
param( |
||||
[string]$Remote = "reverse-proxy", |
||||
[string]$InstallRoot = "/var/www/geonetagent/install" |
||||
) |
||||
|
||||
$ErrorActionPreference = "Stop" |
||||
$RepoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent |
||||
$Agent = Join-Path $RepoRoot "agent" |
||||
$ManifestPath = Join-Path $Agent "install\manifest.json" |
||||
|
||||
if (-not (Test-Path $ManifestPath)) { |
||||
throw "Manifest tidak ditemukan: $ManifestPath" |
||||
} |
||||
|
||||
$manifest = Get-Content $ManifestPath -Raw | ConvertFrom-Json |
||||
$version = (Get-Content (Join-Path $Agent "VERSION") -Raw).Trim() |
||||
Write-Host "==> GeoNetAgent install package v$version (manifest $($manifest.version))" |
||||
|
||||
ssh $Remote "mkdir -p $InstallRoot/lib" |
||||
|
||||
Write-Host "==> Upload agent scripts ($($manifest.files.Count) file)" |
||||
foreach ($rel in $manifest.files) { |
||||
$local = Join-Path $Agent ($rel -replace '/', '\') |
||||
if (-not (Test-Path $local)) { |
||||
throw "File hilang di repo: $rel" |
||||
} |
||||
$destDir = Split-Path $rel -Parent |
||||
if ($destDir) { |
||||
ssh $Remote "mkdir -p `"$InstallRoot/$($destDir -replace '\\','/')`"" |
||||
} |
||||
scp -q $local "${Remote}:${InstallRoot}/$($rel -replace '\\','/')" |
||||
Write-Host " -> $rel" |
||||
} |
||||
|
||||
$extras = @( |
||||
@{ Local = "Install-GeoNetAgent.ps1"; Remote = "Install-GeoNetAgent.ps1" }, |
||||
@{ Local = "install\bootstrap.ps1"; Remote = "bootstrap.ps1" }, |
||||
@{ Local = "install\manifest.json"; Remote = "manifest.json" }, |
||||
@{ Local = "install\GeoNetAgent-Install.ps1"; Remote = "GeoNetAgent-Install.ps1" }, |
||||
@{ Local = "install\index.html"; Remote = "index.html" }, |
||||
@{ Local = "lib\Agent-InstallCore.ps1"; Remote = "lib/Agent-InstallCore.ps1" } |
||||
) |
||||
|
||||
Write-Host "==> Upload installer + manifest" |
||||
foreach ($item in $extras) { |
||||
$local = Join-Path $Agent $item.Local |
||||
scp -q $local "${Remote}:${InstallRoot}/$($item.Remote)" |
||||
} |
||||
|
||||
$configShare = Join-Path $Agent "config.share.json" |
||||
if (Test-Path $configShare) { |
||||
Write-Host "==> Upload config.json" |
||||
scp -q $configShare "${Remote}:${InstallRoot}/config.json" |
||||
} else { |
||||
Write-Host "WARN: config.share.json tidak ada" |
||||
} |
||||
|
||||
ssh $Remote "chown -R nginx:nginx /var/www/geonetagent 2>/dev/null || chown -R root:root /var/www/geonetagent; chmod -R a+rX /var/www/geonetagent" |
||||
|
||||
Write-Host "==> Verifikasi manifest publik" |
||||
$remoteManifest = ssh $Remote "cat $InstallRoot/manifest.json" |
||||
$remoteVersion = ($remoteManifest | ConvertFrom-Json).version |
||||
Write-Host "Remote manifest version: $remoteVersion" |
||||
if ($remoteVersion -ne $manifest.version) { |
||||
throw "Version mismatch: remote=$remoteVersion local=$($manifest.version)" |
||||
} |
||||
|
||||
try { |
||||
$urlManifest = Invoke-RestMethod -Uri "https://agent.gisportal.id/install/manifest.json" -TimeoutSec 15 |
||||
Write-Host "HTTPS manifest version: $($urlManifest.version)" |
||||
if ($urlManifest.version -ne $manifest.version) { |
||||
Write-Warning "HTTPS manifest belum sinkron — cek nginx cache/proxy" |
||||
} |
||||
} catch { |
||||
Write-Warning "Tidak bisa verifikasi HTTPS: $_" |
||||
} |
||||
|
||||
Write-Host "OK: https://agent.gisportal.id/install/" |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
# Upload collector + rebuild Docker di proxy.lan |
||||
# Jalankan dari root repo (PowerShell): |
||||
# .\infra\collector\push_collector.ps1 |
||||
param( |
||||
[string]$Remote = "reverse-proxy", |
||||
[string]$StackDir = "/opt/stacks/geonetagent-collector" |
||||
) |
||||
|
||||
$ErrorActionPreference = "Stop" |
||||
$RepoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent |
||||
$Collector = Join-Path $RepoRoot "collector" |
||||
$Dockerfile = Join-Path $RepoRoot "infra\collector\Dockerfile" |
||||
$TarPath = Join-Path $env:TEMP "geonet-collector.tgz" |
||||
$Stage = Join-Path $env:TEMP "geonet-collector-stage" |
||||
|
||||
if (Test-Path $Stage) { Remove-Item $Stage -Recurse -Force } |
||||
New-Item -ItemType Directory -Path $Stage | Out-Null |
||||
Copy-Item -Path (Join-Path $Collector "*") -Destination $Stage -Recurse -Force |
||||
Copy-Item $Dockerfile (Join-Path $Stage "Dockerfile") -Force |
||||
|
||||
if (Test-Path $TarPath) { Remove-Item $TarPath -Force } |
||||
Write-Host "==> Pack collector" |
||||
tar -czf $TarPath -C $Stage . |
||||
|
||||
Write-Host "==> Upload ke $Remote" |
||||
ssh $Remote "mkdir -p $StackDir" |
||||
scp -q $TarPath "${Remote}:${StackDir}/collector.tgz" |
||||
|
||||
Write-Host "==> Rebuild & restart container" |
||||
$remoteScript = @" |
||||
set -euo pipefail |
||||
cd '$StackDir' |
||||
if [ ! -f .env ]; then |
||||
echo 'ERROR: .env tidak ada di $StackDir' |
||||
exit 1 |
||||
fi |
||||
rm -rf collector-src |
||||
mkdir -p collector-src |
||||
tar -xzf collector.tgz -C collector-src |
||||
docker compose build --no-cache |
||||
docker compose up -d |
||||
sleep 3 |
||||
curl -sf http://127.0.0.1:18000/api/v1/health |
||||
echo |
||||
"@ |
||||
ssh $Remote $remoteScript |
||||
|
||||
Remove-Item $Stage -Recurse -Force -ErrorAction SilentlyContinue |
||||
Write-Host "OK: https://collector.gisportal.id/ui/" |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash |
||||
# Upload collector + rebuild Docker di proxy.lan (10.100.1.24) |
||||
# Jalankan dari repo root: |
||||
# bash infra/collector/push_collector.sh |
||||
set -euo pipefail |
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" |
||||
REMOTE="${REMOTE:-root@10.100.1.24}" |
||||
STACK_DIR="/opt/stacks/geonetagent-collector" |
||||
TMP_PACK="$(mktemp -d)" |
||||
|
||||
cleanup() { rm -rf "$TMP_PACK"; } |
||||
trap cleanup EXIT |
||||
|
||||
if [[ ! -f "$STACK_DIR/.env" ]] 2>/dev/null; then |
||||
: |
||||
fi |
||||
|
||||
echo "==> Pack collector" |
||||
cp -a "$REPO_ROOT/collector/." "$TMP_PACK/" |
||||
cp "$REPO_ROOT/infra/collector/Dockerfile" "$TMP_PACK/Dockerfile" |
||||
tar -czf /tmp/geonet-collector.tgz -C "$TMP_PACK" . |
||||
|
||||
echo "==> Upload ke $REMOTE" |
||||
ssh "$REMOTE" "mkdir -p $STACK_DIR/collector-src" |
||||
scp -q /tmp/geonet-collector.tgz "$REMOTE:$STACK_DIR/collector.tgz" |
||||
|
||||
echo "==> Rebuild & restart container" |
||||
ssh "$REMOTE" bash << 'REMOTE' |
||||
set -euo pipefail |
||||
STACK_DIR="/opt/stacks/geonetagent-collector" |
||||
cd "$STACK_DIR" |
||||
if [[ ! -f .env ]]; then |
||||
echo "ERROR: $STACK_DIR/.env tidak ada. Deploy collector pertama kali via deploy_reverse_proxy.sh" |
||||
exit 1 |
||||
fi |
||||
rm -rf collector-src |
||||
mkdir -p collector-src |
||||
tar -xzf collector.tgz -C collector-src |
||||
docker compose build --no-cache |
||||
docker compose up -d |
||||
sleep 3 |
||||
curl -sf http://127.0.0.1:18000/api/v1/health |
||||
echo |
||||
REMOTE |
||||
|
||||
echo "OK: https://collector.gisportal.id/ui/" |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
-- machine_id = Windows MachineGuid (OS install), not globally unique hardware id. |
||||
-- Disk clone can produce two physical machines with the same machine_id. |
||||
ALTER TABLE assets DROP CONSTRAINT IF EXISTS assets_machine_id_key; |
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assets_machine_id ON assets (machine_id) |
||||
WHERE machine_id IS NOT NULL; |
||||
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
-- GeoNetAgent — view hardware ringkas dari snapshot terbaru per asset |
||||
-- Database: geonetagent_dev / geonetagent |
||||
-- Jalankan: sudo -u postgres psql -d geonetagent_dev -f 004_v_asset_hardware_latest.sql |
||||
|
||||
\set ON_ERROR_STOP on |
||||
|
||||
CREATE OR REPLACE VIEW v_asset_hardware_latest AS |
||||
WITH latest_snapshot AS ( |
||||
SELECT DISTINCT ON (s.asset_id) |
||||
s.id AS snapshot_id, |
||||
s.asset_id, |
||||
s.agent_version, |
||||
s.collected_at, |
||||
s.os_name, |
||||
s.os_version, |
||||
s.cpu_model, |
||||
s.ram_gb, |
||||
s.gpu_model, |
||||
s.payload |
||||
FROM asset_snapshots s |
||||
ORDER BY s.asset_id, s.collected_at DESC, s.created_at DESC |
||||
), |
||||
display_stats AS ( |
||||
SELECT |
||||
ls.asset_id, |
||||
COUNT(*) FILTER ( |
||||
WHERE p.elem->>'connection' = 'display' |
||||
)::integer AS display_count, |
||||
COUNT(*) FILTER ( |
||||
WHERE p.elem->>'connection' = 'display' |
||||
AND COALESCE(p.elem->>'is_external', '') = 'true' |
||||
)::integer AS display_external_count, |
||||
COUNT(*) FILTER ( |
||||
WHERE p.elem->>'connection' = 'display' |
||||
AND COALESCE(p.elem->>'is_external', '') = 'false' |
||||
)::integer AS display_internal_count, |
||||
COALESCE( |
||||
jsonb_agg(p.elem ORDER BY p.ordinality) |
||||
FILTER (WHERE p.elem->>'connection' = 'display'), |
||||
'[]'::jsonb |
||||
) AS displays |
||||
FROM latest_snapshot ls |
||||
LEFT JOIN LATERAL jsonb_array_elements( |
||||
COALESCE(ls.payload->'peripherals', '[]'::jsonb) |
||||
) WITH ORDINALITY AS p(elem, ordinality) ON TRUE |
||||
GROUP BY ls.asset_id |
||||
) |
||||
SELECT |
||||
a.id AS asset_id, |
||||
a.hostname, |
||||
a.display_name, |
||||
a.asset_type, |
||||
a.status, |
||||
a.manufacturer, |
||||
a.model, |
||||
a.serial_number, |
||||
a.smbios_uuid, |
||||
a.machine_id, |
||||
a.primary_physical_mac, |
||||
a.last_seen_at, |
||||
|
||||
ls.snapshot_id, |
||||
ls.agent_version, |
||||
ls.collected_at AS snapshot_collected_at, |
||||
ls.os_name, |
||||
ls.os_version, |
||||
|
||||
COALESCE(ls.payload->'system'->>'form_factor', a.metadata->>'form_factor') AS form_factor, |
||||
COALESCE(ls.payload->'system'->>'chassis_label', a.metadata->>'chassis_label') AS chassis_label, |
||||
ls.payload->'system'->>'pc_system_label' AS pc_system_label, |
||||
|
||||
COALESCE(ls.payload->'cpu'->>'model', ls.cpu_model) AS cpu_model, |
||||
NULLIF(ls.payload->'cpu'->>'manufacturer', '')::varchar AS cpu_manufacturer, |
||||
NULLIF(ls.payload->'cpu'->>'cores', '')::integer AS cpu_cores, |
||||
NULLIF(ls.payload->'cpu'->>'logical_processors', '')::integer AS cpu_logical_processors, |
||||
NULLIF(ls.payload->'cpu'->>'max_clock_mhz', '')::integer AS cpu_max_clock_mhz, |
||||
NULLIF(ls.payload->'cpu'->>'current_clock_mhz', '')::integer AS cpu_current_clock_mhz, |
||||
NULLIF(ls.payload->'cpu'->>'l2_cache_kb', '')::integer AS cpu_l2_cache_kb, |
||||
NULLIF(ls.payload->'cpu'->>'l3_cache_kb', '')::integer AS cpu_l3_cache_kb, |
||||
NULLIF(ls.payload->'cpu'->>'architecture', '')::varchar AS cpu_architecture, |
||||
|
||||
ls.ram_gb, |
||||
NULLIF(ls.payload->'memory_summary'->>'ddr_generation', '')::varchar AS ram_ddr_generation, |
||||
NULLIF(ls.payload->'memory_summary'->>'layout', '')::varchar AS ram_layout, |
||||
NULLIF(ls.payload->'memory_summary'->>'configuration', '')::varchar AS ram_configuration, |
||||
NULLIF(ls.payload->'memory_summary'->>'module_count', '')::integer AS ram_module_count, |
||||
NULLIF(ls.payload->'memory_summary'->>'slot_count', '')::integer AS ram_slot_count, |
||||
NULLIF(ls.payload->'memory_summary'->>'empty_slots', '')::integer AS ram_empty_slots, |
||||
COALESCE(ls.payload->'memory_modules', '[]'::jsonb) AS memory_modules, |
||||
|
||||
ls.gpu_model AS gpu_primary, |
||||
jsonb_array_length(COALESCE(ls.payload->'gpus', '[]'::jsonb))::integer AS gpu_count, |
||||
COALESCE(ls.payload->'gpus', '[]'::jsonb) AS gpus, |
||||
|
||||
COALESCE(ds.display_count, 0) AS display_count, |
||||
COALESCE(ds.display_external_count, 0) AS display_external_count, |
||||
COALESCE(ds.display_internal_count, 0) AS display_internal_count, |
||||
COALESCE(ds.displays, '[]'::jsonb) AS displays |
||||
|
||||
FROM assets a |
||||
LEFT JOIN latest_snapshot ls ON ls.asset_id = a.id |
||||
LEFT JOIN display_stats ds ON ds.asset_id = a.id |
||||
WHERE a.deleted_at IS NULL; |
||||
|
||||
COMMENT ON VIEW v_asset_hardware_latest IS |
||||
'Satu baris per asset: CPU/GPU/RAM/display dari asset_snapshots terbaru (payload JSONB).'; |
||||
|
||||
GRANT SELECT ON v_asset_hardware_latest TO geonet_collector, geonet_portal; |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
-- GeoNetAgent — asset invoices, tickets, document attachments (QNAP Object Storage metadata) |
||||
-- ADR: .cursor/adr/003-asset-documents-qnap.md |
||||
-- Fase 3 — jalankan setelah portal Laravel scaffold |
||||
-- Database: geonetagent_dev / geonetagent |
||||
|
||||
\set ON_ERROR_STOP on |
||||
|
||||
-- 1. asset_invoices (procurement) |
||||
CREATE TABLE IF NOT EXISTS asset_invoices ( |
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
||||
asset_id uuid NOT NULL REFERENCES assets(id) ON DELETE CASCADE, |
||||
invoice_number varchar(100) NOT NULL, |
||||
invoice_date date, |
||||
vendor varchar(200), |
||||
purchase_price numeric(14, 2), |
||||
currency varchar(3) NOT NULL DEFAULT 'IDR', |
||||
po_number varchar(100), |
||||
warranty_until date, |
||||
serial_at_purchase varchar(100), |
||||
notes text, |
||||
created_by varchar(100), |
||||
created_at timestamptz NOT NULL DEFAULT now(), |
||||
updated_at timestamptz NOT NULL DEFAULT now(), |
||||
deleted_at timestamptz, |
||||
CONSTRAINT uq_asset_invoices_number UNIQUE (invoice_number) |
||||
); |
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_asset_invoices_asset ON asset_invoices (asset_id); |
||||
CREATE INDEX IF NOT EXISTS idx_asset_invoices_date ON asset_invoices (invoice_date DESC); |
||||
|
||||
CREATE TRIGGER trg_asset_invoices_updated_at |
||||
BEFORE UPDATE ON asset_invoices |
||||
FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); |
||||
|
||||
-- 2. tickets (service + complaint) |
||||
CREATE TABLE IF NOT EXISTS tickets ( |
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
||||
ticket_number varchar(30) NOT NULL, |
||||
ticket_type varchar(20) NOT NULL, |
||||
status varchar(20) NOT NULL DEFAULT 'open', |
||||
priority varchar(10) NOT NULL DEFAULT 'medium', |
||||
asset_id uuid REFERENCES assets(id) ON DELETE SET NULL, |
||||
reporter_user_id uuid REFERENCES users(id) ON DELETE SET NULL, |
||||
assigned_to_user_id uuid REFERENCES users(id) ON DELETE SET NULL, |
||||
subject varchar(255) NOT NULL, |
||||
description text, |
||||
resolution_notes text, |
||||
opened_at timestamptz NOT NULL DEFAULT now(), |
||||
resolved_at timestamptz, |
||||
closed_at timestamptz, |
||||
created_at timestamptz NOT NULL DEFAULT now(), |
||||
updated_at timestamptz NOT NULL DEFAULT now(), |
||||
deleted_at timestamptz, |
||||
CONSTRAINT uq_tickets_number UNIQUE (ticket_number), |
||||
CONSTRAINT chk_tickets_type CHECK (ticket_type IN ('service', 'complaint')), |
||||
CONSTRAINT chk_tickets_status CHECK (status IN ( |
||||
'open', 'in_progress', 'waiting_user', 'resolved', 'closed', 'cancelled' |
||||
)), |
||||
CONSTRAINT chk_tickets_priority CHECK (priority IN ('low', 'medium', 'high', 'urgent')) |
||||
); |
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tickets_asset ON tickets (asset_id); |
||||
CREATE INDEX IF NOT EXISTS idx_tickets_type_status ON tickets (ticket_type, status); |
||||
CREATE INDEX IF NOT EXISTS idx_tickets_opened ON tickets (opened_at DESC); |
||||
|
||||
CREATE TRIGGER trg_tickets_updated_at |
||||
BEFORE UPDATE ON tickets |
||||
FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); |
||||
|
||||
-- 3. document_attachments (shared — metadata file di QNAP S3) |
||||
CREATE TABLE IF NOT EXISTS document_attachments ( |
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), |
||||
domain varchar(20) NOT NULL, |
||||
kind varchar(40) NOT NULL DEFAULT 'other', |
||||
asset_id uuid REFERENCES assets(id) ON DELETE CASCADE, |
||||
invoice_id uuid REFERENCES asset_invoices(id) ON DELETE CASCADE, |
||||
ticket_id uuid REFERENCES tickets(id) ON DELETE CASCADE, |
||||
storage_provider varchar(20) NOT NULL DEFAULT 'qnap_s3', |
||||
bucket varchar(100) NOT NULL, |
||||
object_key varchar(500) NOT NULL, |
||||
original_filename varchar(255) NOT NULL, |
||||
content_type varchar(100), |
||||
file_size_bytes bigint, |
||||
checksum_sha256 varchar(64), |
||||
uploaded_by varchar(100), |
||||
uploaded_at timestamptz NOT NULL DEFAULT now(), |
||||
metadata jsonb NOT NULL DEFAULT '{}', |
||||
deleted_at timestamptz, |
||||
CONSTRAINT chk_document_attachments_domain CHECK (domain IN ( |
||||
'procurement', 'service', 'complaint' |
||||
)), |
||||
CONSTRAINT chk_document_attachments_parent CHECK ( |
||||
asset_id IS NOT NULL OR invoice_id IS NOT NULL OR ticket_id IS NOT NULL |
||||
), |
||||
CONSTRAINT uq_document_attachments_object UNIQUE (bucket, object_key) |
||||
); |
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_document_attachments_asset ON document_attachments (asset_id) |
||||
WHERE deleted_at IS NULL; |
||||
CREATE INDEX IF NOT EXISTS idx_document_attachments_invoice ON document_attachments (invoice_id) |
||||
WHERE deleted_at IS NULL; |
||||
CREATE INDEX IF NOT EXISTS idx_document_attachments_ticket ON document_attachments (ticket_id) |
||||
WHERE deleted_at IS NULL; |
||||
CREATE INDEX IF NOT EXISTS idx_document_attachments_domain ON document_attachments (domain, kind); |
||||
|
||||
COMMENT ON TABLE asset_invoices IS 'Procurement — invoice/PO metadata; file di QNAP via document_attachments'; |
||||
COMMENT ON TABLE tickets IS 'Service & complaint tickets; lampiran di document_attachments'; |
||||
COMMENT ON TABLE document_attachments IS 'Metadata file di QNAP Object Storage (ADR 003)'; |
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON asset_invoices, tickets, document_attachments TO geonet_collector; |
||||
GRANT SELECT, INSERT, UPDATE ON asset_invoices, tickets, document_attachments TO geonet_portal; |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
# QNAP Object Storage — GeoNetAgent Documents |
||||
|
||||
> NAS: TS-932X · `10.100.1.10` · SSH: `infra/host.md` |
||||
> ADR: `.cursor/adr/003-asset-documents-qnap.md` |
||||
|
||||
## Ringkasan |
||||
|
||||
| Item | Nilai | |
||||
|------|--------| |
||||
| Bucket | `geonetagent-documents` | |
||||
| API | S3-compatible (QNAP Object Storage) | |
||||
| Akses | LAN internal; kredensial khusus portal | |
||||
| Isi | PDF invoice, foto unit, dokumen service, lampiran complain | |
||||
|
||||
## Setup (sekali, di QNAP) |
||||
|
||||
1. **Control Panel** → Object Storage → aktifkan / pastikan service jalan |
||||
2. Buat bucket `geonetagent-documents` |
||||
3. Buat user/API key khusus `geonetagent-portal` — hanya bucket ini (read/write) |
||||
4. Catat endpoint, access key, secret → portal `.env`: |
||||
|
||||
```env |
||||
S3_ENDPOINT=http://10.100.1.10:PORT |
||||
S3_BUCKET=geonetagent-documents |
||||
S3_ACCESS_KEY_ID=... |
||||
S3_SECRET_ACCESS_KEY=... |
||||
S3_REGION=us-east-1 |
||||
``` |
||||
|
||||
(Ganti `PORT` sesuai konfigurasi QNAP Object Storage.) |
||||
|
||||
## Object key layout |
||||
|
||||
``` |
||||
{domain}/{yyyy}/{mm}/{attachment_uuid}/{filename} |
||||
|
||||
procurement/2026/06/{uuid}/INV-2024-00123.pdf |
||||
service/2026/06/{uuid}/ba-perbaikan.pdf |
||||
complaint/2026/06/{uuid}/screenshot.png |
||||
``` |
||||
|
||||
`attachment_uuid` = `document_attachments.id` |
||||
|
||||
## Database |
||||
|
||||
Migration: `infra/db/migrations/005_asset_documents.sql` (jalankan saat portal Fase 3). |
||||
|
||||
## Yang tidak disimpan di sini |
||||
|
||||
- Hardware snapshot agent → PostgreSQL `asset_snapshots.payload` |
||||
- Alert Zabbix → PostgreSQL `alerts` (+ Zabbix master) |
||||
@ -0,0 +1,404 @@
@@ -0,0 +1,404 @@
|
||||
{ |
||||
"schema_version": "1.0", |
||||
"agent_version": "0.1.10-phase0", |
||||
"collected_at": "2026-06-22T13:27:06+07:00", |
||||
"hostname": "DESKTOP-RRJ9G01", |
||||
"machine_id": "3709baa5-5265-4e4d-9a81-fb8c8927bb3b", |
||||
"smbios_uuid": "03D502E0-045E-0550-8E06-BA0700080009", |
||||
"serial_number": null, |
||||
"asset_type": "endpoint", |
||||
"logged_in_user": "DESKTOP-RRJ9G01\\admin", |
||||
"system": { |
||||
"manufacturer": "Gigabyte Technology Co., Ltd.", |
||||
"model": "H81M-DS2", |
||||
"bios_version": "F2", |
||||
"os_name": "Microsoft Windows 11 Pro", |
||||
"os_version": "10.0", |
||||
"os_build": "10.0.26200 (26200)", |
||||
"cpu_model": "Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz", |
||||
"ram_gb": 15.9, |
||||
"gpu_model": "NVIDIA GeForce GT 730", |
||||
"last_boot_at": "2026-06-22T09:42:14+07:00", |
||||
"form_factor": "desktop", |
||||
"chassis_label": "Desktop", |
||||
"chassis_types": [ |
||||
3 |
||||
], |
||||
"pc_system_type": 1, |
||||
"pc_system_label": "Desktop" |
||||
}, |
||||
"cpu": { |
||||
"model": "Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz", |
||||
"manufacturer": "GenuineIntel", |
||||
"cores": 4, |
||||
"logical_processors": 4, |
||||
"max_clock_mhz": 3201, |
||||
"current_clock_mhz": 3201, |
||||
"socket": "SOCKET 0", |
||||
"socket_count": 1, |
||||
"l2_cache_kb": 1024, |
||||
"l3_cache_kb": 6144, |
||||
"architecture": "x64" |
||||
}, |
||||
"gpus": [ |
||||
{ |
||||
"name": "NVIDIA GeForce GT 730", |
||||
"adapter_compatibility": "NVIDIA", |
||||
"video_processor": "GeForce GT 730", |
||||
"driver_version": "23.21.13.8813", |
||||
"driver_date": "2017-10-27T07:00:00+07:00", |
||||
"dedicated_memory_mb": 2048, |
||||
"pnp_device_id": "PCI\\VEN_10DE\u0026DEV_0F02\u0026SUBSYS_00000000\u0026REV_A1\\4\u00261286464\u00260\u00260008", |
||||
"status": "OK", |
||||
"current_resolution": "1920x1080", |
||||
"video_mode_description": "1920 x 1080 x 4294967296 colors" |
||||
}, |
||||
{ |
||||
"name": "Intel(R) HD Graphics 4600", |
||||
"adapter_compatibility": "Intel Corporation", |
||||
"video_processor": "Intel(R) HD Graphics Family", |
||||
"driver_version": "20.19.15.4531", |
||||
"driver_date": "2016-09-29T07:00:00+07:00", |
||||
"dedicated_memory_mb": 1024, |
||||
"pnp_device_id": "PCI\\VEN_8086\u0026DEV_0412\u0026SUBSYS_D0001458\u0026REV_06\\3\u002611583659\u00260\u002610", |
||||
"status": "OK", |
||||
"current_resolution": "1366x768", |
||||
"video_mode_description": "1366 x 768 x 4294967296 colors" |
||||
} |
||||
], |
||||
"disks": [ |
||||
{ |
||||
"drive_letter": "C:", |
||||
"total_gb": 188.7, |
||||
"free_gb": 4.5, |
||||
"pct_used": 97.6, |
||||
"file_system": "NTFS", |
||||
"model": "ADATA SU650" |
||||
}, |
||||
{ |
||||
"drive_letter": "D:", |
||||
"total_gb": 256.5, |
||||
"free_gb": 68.4, |
||||
"pct_used": 73.3, |
||||
"file_system": "NTFS", |
||||
"model": "ADATA SU650" |
||||
} |
||||
], |
||||
"physical_disks": [ |
||||
{ |
||||
"disk_number": 0, |
||||
"device_id": "0", |
||||
"model": "ADATA SU650", |
||||
"friendly_name": "ADATA SU650", |
||||
"media_type": "SSD", |
||||
"operational_status": "OK", |
||||
"health_status": "Healthy", |
||||
"total_gb": 447.1, |
||||
"partition_style": "MBR", |
||||
"bus_type": "SATA", |
||||
"serial_number": "2N29292H8BN9" |
||||
} |
||||
], |
||||
"memory_modules": [ |
||||
{ |
||||
"slot": "ChannelA-DIMM0", |
||||
"bank_label": "BANK 0", |
||||
"capacity_gb": 8, |
||||
"speed_mhz": 1600, |
||||
"configured_speed_mhz": 1600, |
||||
"manufacturer": "0000", |
||||
"part_number": "", |
||||
"serial_number": "00000000", |
||||
"form_factor": "tsop", |
||||
"memory_type": 24, |
||||
"memory_type_label": "DDR3", |
||||
"data_width": 64, |
||||
"total_width": 64, |
||||
"rank": null, |
||||
"configured_voltage_mv": null |
||||
}, |
||||
{ |
||||
"slot": "ChannelB-DIMM0", |
||||
"bank_label": "BANK 2", |
||||
"capacity_gb": 8, |
||||
"speed_mhz": 1600, |
||||
"configured_speed_mhz": 1600, |
||||
"manufacturer": "0000", |
||||
"part_number": "", |
||||
"serial_number": "00000000", |
||||
"form_factor": "tsop", |
||||
"memory_type": 24, |
||||
"memory_type_label": "DDR3", |
||||
"data_width": 64, |
||||
"total_width": 64, |
||||
"rank": null, |
||||
"configured_voltage_mv": null |
||||
} |
||||
], |
||||
"memory_summary": { |
||||
"total_gb": 16, |
||||
"module_count": 2, |
||||
"slot_count": 2, |
||||
"empty_slots": 0, |
||||
"layout": "2x8GB", |
||||
"configuration": "2x8GB tsop DDR3", |
||||
"upgradeable": false, |
||||
"ddr_generation": "DDR3" |
||||
}, |
||||
"network": [ |
||||
{ |
||||
"adapter_name": "Realtek(R) PCI(e) Ethernet Controller", |
||||
"mac_address": "E0:D5:5E:50:8E:BA", |
||||
"ip_address": "192.168.0.21", |
||||
"adapter_kind": "physical", |
||||
"status": "Up", |
||||
"is_primary": true, |
||||
"is_built_in": true |
||||
}, |
||||
{ |
||||
"adapter_name": "Fortinet SSL VPN Virtual Ethernet Adapter", |
||||
"mac_address": "00:09:0F:AA:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Not Present", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "Fortinet Virtual Ethernet Adapter (NDIS 6.30)", |
||||
"mac_address": "00:09:0F:FE:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "Hyper-V Virtual Ethernet Adapter", |
||||
"mac_address": "00:15:5D:F1:DB:09", |
||||
"ip_address": "172.24.128.1", |
||||
"adapter_kind": "virtual", |
||||
"status": "Up", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "OpenVPN Data Channel Offload", |
||||
"mac_address": null, |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "PANGP Virtual Ethernet Adapter Secure #2", |
||||
"mac_address": "02:50:41:00:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disabled", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "TAP-Windows Adapter V9 for OpenVPN Connect", |
||||
"mac_address": "00:FF:E0:E1:4D:4D", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
} |
||||
], |
||||
"asset_identity": { |
||||
"primary_physical_mac": "E0:D5:5E:50:8E:BA", |
||||
"primary_physical_adapter": "Realtek(R) PCI(e) Ethernet Controller", |
||||
"primary_mac_built_in": true, |
||||
"primary_mac_source": "primary_route_built_in_physical", |
||||
"merge_key": "mac:E0:D5:5E:50:8E:BA", |
||||
"recommended_match_order": [ |
||||
"serial_number", |
||||
"smbios_uuid", |
||||
"primary_physical_mac", |
||||
"machine_id", |
||||
"hostname" |
||||
] |
||||
}, |
||||
"peripherals": [ |
||||
{ |
||||
"name": "E1942", |
||||
"device_class": "Monitor", |
||||
"manufacturer": "GSM", |
||||
"connection": "display", |
||||
"device_role": "display", |
||||
"vid": null, |
||||
"pid": null, |
||||
"pnp_device_id": "DISPLAY\\GSM4C09\\5\u0026316DE597\u00260\u0026UID4865", |
||||
"device_serial": "211INNG5R639", |
||||
"edid_product": "GSM4C09", |
||||
"resolution": "1920x1080", |
||||
"resolution_width": 1920, |
||||
"resolution_height": 1080, |
||||
"refresh_hz": 60, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "Q27G4", |
||||
"device_class": "Monitor", |
||||
"manufacturer": "AOC", |
||||
"connection": "display", |
||||
"device_role": "display", |
||||
"vid": null, |
||||
"pid": null, |
||||
"pnp_device_id": "DISPLAY\\AOCB403\\5\u0026316DE597\u00260\u0026UID4353", |
||||
"device_serial": "1O0Q7HA002010", |
||||
"edid_product": "AOCB403", |
||||
"resolution": "1366x768", |
||||
"resolution_width": 1366, |
||||
"resolution_height": 768, |
||||
"refresh_hz": 60, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "USB Input Device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb", |
||||
"device_role": "peripheral", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_01\\8\u0026104DB858\u00260\u00260001", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "USB Input Device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb", |
||||
"device_role": "peripheral", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_00\\8\u0026104DB858\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant consumer control device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "Microsoft", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL03\\9\u00262CF1CDFF\u00260\u00260002", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant system controller", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL02\\9\u00262CF1CDFF\u00260\u00260001", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant vendor-defined device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL04\\9\u00262CF1CDFF\u00260\u00260003", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID Keyboard Device", |
||||
"device_class": "Keyboard", |
||||
"manufacturer": "(Standard keyboards)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_00\\9\u002638016031\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant mouse", |
||||
"device_class": "Mouse", |
||||
"manufacturer": "Microsoft", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL01\\9\u00262CF1CDFF\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
} |
||||
], |
||||
"health": { |
||||
"bsod_count_30d": 10, |
||||
"pending_reboot": false, |
||||
"last_patch_installed": "2026-02-27" |
||||
}, |
||||
"software": [ |
||||
|
||||
], |
||||
"metadata": { |
||||
"collect_mode": "phase0-local", |
||||
"software_included": false, |
||||
"software_count": 0 |
||||
} |
||||
} |
||||
Loading…
Reference in new issue