diff --git a/.gitignore b/.gitignore index 7246e4a..4e08bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ ops/ support/ support-api/ LDAPWeb.code-workspace +mysupperapps_FE_PWA/ # Environment & secrets .env diff --git a/GeoNetAgent/ISO.md b/GeoNetAgent/ISO.md new file mode 100644 index 0000000..fd267a0 --- /dev/null +++ b/GeoNetAgent/ISO.md @@ -0,0 +1,115 @@ +# Standar Internasional — Acuan Masa Pakai & EOL Perangkat IT + +Dokumen ini merangkum standar internasional yang menjadi acuan dalam menentukan masa pakai (*useful life*), siklus hidup, dan *End-of-Life* (EOL) perangkat laptop, PC, dan workstation di lingkungan organisasi. + +--- + +## Ringkasan Standar + +| Standar | Nama Lengkap | Fungsi Utama | +| ------- | ------------ | ------------ | +| ISO 55001 | Asset Management — Management Systems | Asset Lifecycle | +| ISO/IEC 19770-1 | IT Asset Management | IT Asset Management | +| ISO/IEC 20000-1 | IT Service Management | IT Service Management | +| ISO/IEC 27001 | Information Security Management | Information Security | +| NIST SP 800-53 | Security & Privacy Controls for IS | Hardware Lifecycle & Security Controls | +| ISACA COBIT | Control Objectives for IT | Governance & Asset Lifecycle | + +--- + +## Detail Standar & Relevansinya untuk Perangkat Endpoint + +### 1. ISO 55001 — Manajemen Aset (Asset Lifecycle) + +**Relevansi:** Standar utama untuk pengelolaan siklus hidup aset fisik, termasuk perangkat IT. + +- Mewajibkan organisasi mendefinisikan **masa pakai aset** secara eksplisit berdasarkan risiko dan kebutuhan bisnis. +- Memerlukan perencanaan **penghapusan aset** (*asset disposal*) yang terstruktur ketika perangkat mencapai EOL. +- Acuan umum masa pakai perangkat berdasarkan praktik ISO 55001: + - **Laptop/PC pengguna umum:** 3–5 tahun + - **Workstation teknis/desain:** 4–6 tahun + - **Server/infrastruktur:** 5–7 tahun +- Mensyaratkan dokumentasi kondisi aset, jadwal penggantian, dan analisis biaya siklus hidup penuh (*lifecycle cost analysis*). + +--- + +### 2. ISO/IEC 19770-1 — IT Asset Management (ITAM) + +**Relevansi:** Standar spesifik untuk pengelolaan aset IT, mencakup hardware dan software. + +- Mendefinisikan proses **inventarisasi, pelacakan, dan penonaktifan** aset IT secara sistematis. +- Mensyaratkan pencatatan atribut EOL/EOS (*End-of-Support*) dari setiap perangkat, termasuk: + - Tanggal akhir dukungan hardware dari vendor (OEM EOL) + - Tanggal akhir dukungan OS dari Microsoft/Apple/Linux distro +- Mensyaratkan proses **decommissioning** yang mencakup penghapusan data aman (*secure data wipe*) sebelum perangkat dilepas. +- Tiga tingkat kematangan ITAM (Tier 1–3); untuk pengelolaan EOL yang efektif minimal Tier 2. + +--- + +### 3. ISO/IEC 20000-1 — IT Service Management (ITSM) + +**Relevansi:** Menghubungkan siklus hidup perangkat dengan manajemen layanan IT. + +- Proses **Configuration Management** (CMDB) mewajibkan pencatatan status aset termasuk status EOL/aktif/pensiun. +- Proses **Change Management** mensyaratkan perencanaan penggantian perangkat yang mendekati EOL agar tidak mengganggu layanan. +- Proses **Capacity & Availability Management** menggunakan data EOL untuk merencanakan kapasitas dan anggaran pengadaan. + +--- + +### 4. ISO/IEC 27001 — Information Security Management + +**Relevansi:** Keamanan informasi mewajibkan kontrol ketat pada perangkat yang mendekati atau melewati EOL. + +- Kontrol **A.8 — Asset Management**: semua aset informasi (termasuk endpoint) harus diinventarisasi dan diketahui status EOL-nya. +- Perangkat yang OS-nya sudah EOL (contoh: Windows 7, Windows 10 setelah Okt 2025) dianggap **risiko keamanan tinggi** karena tidak mendapat patch keamanan. +- Mensyaratkan prosedur penghapusan data aman saat perangkat diretire (*media sanitization*). +- Pengelolaan EOL yang buruk dapat menjadi temuan **nonkonformitas** saat audit ISO 27001. + +--- + +### 5. NIST SP 800-53 — Hardware Lifecycle & Security Controls + +**Relevansi:** Panduan kontrol keamanan hardware dari NIST (banyak diadopsi sebagai referensi di luar AS). + +- **SA-22 (Unsupported System Components):** Mewajibkan organisasi untuk tidak mengoperasikan komponen IT yang sudah tidak didukung vendor (EOL/EOS), atau mendokumentasikan mitigasi risiko yang memadai. +- **MA-6 (Timely Maintenance):** Mensyaratkan perawatan dan penggantian tepat waktu berdasarkan jadwal siklus hidup. +- **SI-2 (Flaw Remediation):** Perangkat EOL tidak dapat menerima patch, secara otomatis melanggar kontrol ini. + +--- + +### 6. ISACA COBIT — Governance & Asset Lifecycle + +**Relevansi:** Kerangka tata kelola IT yang menghubungkan pengelolaan aset dengan nilai bisnis. + +- **BAI09 (Manage Assets):** Proses khusus untuk inventarisasi, pemeliharaan, dan penonaktifan aset IT. +- **DSS01 (Manage Operations):** Mensyaratkan pemantauan kondisi hardware secara berkelanjutan. +- Pengelolaan EOL masuk dalam konteks **risk management** dan **value delivery** — perangkat yang melewati EOL meningkatkan risiko operasional dan biaya tidak terduga. + +--- + +## Acuan Masa Pakai Umum Perangkat Endpoint + +| Jenis Perangkat | Masa Pakai Ideal | Batas Maksimum | Catatan | +| --------------- | ---------------- | -------------- | ------- | +| Laptop (pengguna umum) | 3–4 tahun | 5 tahun | Berdasarkan siklus depresiasi fiskal & EOL OS | +| PC Desktop (pengguna umum) | 4–5 tahun | 6 tahun | Komponen lebih mudah diupgrade | +| Workstation (teknis/CAD/GIS) | 4–5 tahun | 6 tahun | Tergantung kebutuhan performa aplikasi | +| Laptop/PC manajemen | 3–5 tahun | 5 tahun | Prioritas keamanan data tinggi | + +> **Catatan:** Masa pakai dihitung sejak tanggal pembelian pertama, bukan sejak mulai digunakan. Perangkat yang OS-nya mencapai EOL **wajib diganti atau diupgrade OS** terlepas dari usia hardware. + +--- + +## Referensi EOL OS Umum + +| Sistem Operasi | Tanggal EOL | Keterangan | +| -------------- | ----------- | ---------- | +| Windows 10 (semua edisi) | 14 Oktober 2025 | Tidak ada Extended Security Update gratis untuk konsumen | +| Windows 11 22H2 | 8 Oktober 2024 | Sudah EOL, wajib upgrade ke 23H2/24H2 | +| Windows 11 23H2 | 11 November 2025 | — | +| Windows 11 24H2 | Oktober 2026 (est.) | Rilis terbaru (2024) | +| Ubuntu 20.04 LTS | April 2025 (standard) | ESM hingga April 2030 | +| Ubuntu 22.04 LTS | April 2027 (standard) | ESM hingga April 2032 | +| Ubuntu 24.04 LTS | April 2029 (standard) | ESM hingga April 2034 | + +> Sumber: Microsoft Lifecycle Policy, Ubuntu Release Schedule. Selalu verifikasi ke situs resmi vendor untuk tanggal terkini. diff --git a/GeoNetAgent/agent/VERSION b/GeoNetAgent/agent/VERSION index a5a2e50..4ca719c 100644 --- a/GeoNetAgent/agent/VERSION +++ b/GeoNetAgent/agent/VERSION @@ -1 +1 @@ -0.1.10-phase0 +0.1.12-phase0 diff --git a/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.cmd b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.cmd new file mode 100644 index 0000000..d1554f0 --- /dev/null +++ b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.cmd @@ -0,0 +1,6 @@ +@echo off +REM GeoNetAgent — GPO Computer Shutdown Script (wrapper) +REM Deploy folder ini ke SYSVOL / NETLOGON, lalu tautkan .cmd ini di GPO Shutdown. + +powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0GeoNetAgent-GPO-Shutdown.ps1" +exit /b 0 diff --git a/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.ps1 b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.ps1 new file mode 100644 index 0000000..3181ac4 --- /dev/null +++ b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Shutdown.ps1 @@ -0,0 +1,59 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + GPO Shutdown Script — kirim laporan GeoNetAgent sebelum Windows shutdown/restart. + +.DESCRIPTION + Dipanggil dari Group Policy (Computer Configuration → Shutdown Scripts). + Menjalankan UserAgent.ps1 -SkipAutoUpdate agar laporan terkirim sebelum PC mati. + Auto-update dilewati karena waktu shutdown terbatas. + +.NOTES + Log: %ProgramData%\GeoNetAgent\logs\gpo-shutdown.log + Exit selalu 0 agar GPO tidak error. + Batas waktu eksekusi GPO Shutdown Script default: 600 detik (bisa diatur via GPO). + Rekomendasikan set "Maximum wait time for Group Policy scripts" ke 60 detik. +#> +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Continue' + +$InstallRoot = Join-Path $env:ProgramData 'GeoNetAgent' +$LogDir = Join-Path $InstallRoot 'logs' +$LogFile = Join-Path $LogDir 'gpo-shutdown.log' +$UserAgent = Join-Path $InstallRoot 'UserAgent.ps1' + +function Write-GeoNetShutdownLog { + param([string]$Message) + $line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message + try { + if (-not (Test-Path $LogDir)) { + New-Item -ItemType Directory -Path $LogDir -Force | Out-Null + } + Add-Content -Path $LogFile -Value $line -Encoding UTF8 + } + catch { } +} + +Write-GeoNetShutdownLog '=== GPO shutdown begin ===' + +try { + if (-not (Test-Path $UserAgent)) { + Write-GeoNetShutdownLog "UserAgent.ps1 tidak ditemukan di $InstallRoot — skip" + exit 0 + } + + Write-GeoNetShutdownLog 'Mengirim laporan sebelum shutdown...' + & powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden ` + -File $UserAgent -SkipAutoUpdate + + $code = $LASTEXITCODE + Write-GeoNetShutdownLog "UserAgent selesai (exit $code)" +} +catch { + Write-GeoNetShutdownLog "ERROR: $($_.Exception.Message)" +} + +Write-GeoNetShutdownLog '=== GPO shutdown end ===' +exit 0 diff --git a/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Startup.ps1 b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Startup.ps1 index aefa6b3..4703e76 100644 --- a/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Startup.ps1 +++ b/GeoNetAgent/agent/gpo/GeoNetAgent-GPO-Startup.ps1 @@ -108,7 +108,7 @@ function Invoke-GeoNetGpoWebInstall { } function Test-GeoNetScheduledTasks { - $names = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') + $names = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup', 'GeoNetAgent-Report-Shutdown') foreach ($name in $names) { $null = & schtasks.exe /Query /TN $name /FO LIST 2>$null if ($LASTEXITCODE -ne 0) { return $false } diff --git a/GeoNetAgent/agent/gpo/README.md b/GeoNetAgent/agent/gpo/README.md index 12649f9..68f4f65 100644 --- a/GeoNetAgent/agent/gpo/README.md +++ b/GeoNetAgent/agent/gpo/README.md @@ -6,8 +6,10 @@ Skrip startup untuk **Computer Configuration** Active Directory. Jalan sebagai * | File | Fungsi | |------|--------| -| `GeoNetAgent-GPO-Startup.cmd` | Entry point untuk GPO (tambahkan file ini di policy) | -| `GeoNetAgent-GPO-Startup.ps1` | Logika install / update / perbaiki task | +| `GeoNetAgent-GPO-Startup.cmd` | Entry point GPO Startup (tambahkan di Startup Scripts) | +| `GeoNetAgent-GPO-Startup.ps1` | Logika install / update / perbaiki task saat boot | +| `GeoNetAgent-GPO-Shutdown.cmd` | Entry point GPO Shutdown (tambahkan di Shutdown Scripts) | +| `GeoNetAgent-GPO-Shutdown.ps1` | Kirim laporan sebelum PC mati / restart | | `README.md` | Panduan ini | ## Prasyarat server @@ -43,7 +45,15 @@ Pastikan **Computer Account** domain bisa **Read** folder tersebut. \\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\GeoNetAgent-GPO-Startup.cmd ``` -5. **OK** → tutup editor GPO +5. Tab **Shutdown** → **Add** → pilih: + + ``` + \\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\GeoNetAgent-GPO-Shutdown.cmd + ``` + +6. **OK** → tutup editor GPO + +> **Tip:** Set `Computer Configuration → Policies → Windows Settings → Scripts → Maximum wait time for Group Policy scripts` ke **60 detik** agar shutdown tidak terlalu lama menunggu agent. ## Langkah 3 — Link ke OU @@ -66,14 +76,18 @@ Setelah boot, cek: Get-Content "$env:ProgramData\GeoNetAgent\VERSION" Get-Content "$env:ProgramData\GeoNetAgent\logs\gpo-startup.log" -Tail 20 schtasks /Query /TN GeoNetAgent-Report /V /FO LIST +schtasks /Query /TN GeoNetAgent-Report-Startup /V /FO LIST +schtasks /Query /TN GeoNetAgent-Report-Shutdown /V /FO LIST Test-Path "$env:ProgramData\GeoNetAgent\UserAgent.ps1" ``` Harapan: -- `VERSION` = versi terbaru di server (mis. `0.1.8-phase0`) +- `VERSION` = versi terbaru di server (mis. `0.1.11-phase0`) - Log berisi `Selesai OK` - Task `GeoNetAgent-Report` — HOURLY, interval **3** jam, run as **SYSTEM** +- Task `GeoNetAgent-Report-Startup` — At Startup, run as **SYSTEM** +- Task `GeoNetAgent-Report-Shutdown` — Event trigger EventID 1074 (shutdown/restart), run as **SYSTEM** ## Perilaku skrip @@ -82,25 +96,44 @@ Harapan: | Agent belum ada | Install penuh dari web (silent, `-SkipInitialRun`) | | Agent lama tanpa modul auto-update | Reinstall | | Agent ada | Cek `manifest.json` vs `VERSION`, update jika beda | -| Scheduled Task hilang | Daftar ulang (startup + 3 jam) | +| Scheduled Task hilang | Daftar ulang (startup + 3 jam + shutdown event) | | Jaringan belum siap saat boot | Tunggu hingga 120 detik | | Error | Catat di log, **exit 0** (boot tidak terganggu) | -Laporan inventaris dikirim oleh Scheduled Task (`UserAgent.ps1`), bukan langsung dari skrip GPO. +### Kapan laporan dikirim + +| Trigger | Keterangan | +|---------|------------| +| **Boot / Startup** | GPO Startup + Task `GeoNetAgent-Report-Startup` (ONSTART) | +| **Setiap 3 jam** | Task `GeoNetAgent-Report` (HOURLY) | +| **Shutdown / Restart** | GPO Shutdown + Task `GeoNetAgent-Report-Shutdown` (Event 1074) | + +Task shutdown menggunakan `-SkipAutoUpdate` agar tidak delay proses mati PC. ## Log ``` -%ProgramData%\GeoNetAgent\logs\gpo-startup.log +%ProgramData%\GeoNetAgent\logs\gpo-startup.log ← startup +%ProgramData%\GeoNetAgent\logs\gpo-shutdown.log ← shutdown +``` + +Contoh startup: + +``` +2026-06-29 08:15:02 === GPO startup begin === +2026-06-29 08:15:08 Agent ada — cek update +2026-06-29 08:15:12 Updated: 0.1.10-phase0 -> 0.1.11-phase0 +2026-06-29 08:15:13 Selesai OK. VERSION=0.1.11-phase0 +2026-06-29 08:15:13 === GPO startup end === ``` -Contoh: +Contoh shutdown: ``` -2026-06-18 08:15:02 === GPO startup begin === -2026-06-18 08:15:08 Agent belum terpasang — install baru -2026-06-18 08:15:45 Selesai OK. VERSION=0.1.8-phase0 -2026-06-18 08:15:45 === GPO startup end === +2026-06-29 17:30:01 === GPO shutdown begin === +2026-06-29 17:30:01 Mengirim laporan sebelum shutdown... +2026-06-29 17:30:08 UserAgent selesai (exit 0) +2026-06-29 17:30:08 === GPO shutdown end === ``` ## Firewall / proxy @@ -127,6 +160,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~d ```cmd schtasks /Delete /TN GeoNetAgent-Report /F schtasks /Delete /TN GeoNetAgent-Report-Startup /F + schtasks /Delete /TN GeoNetAgent-Report-Shutdown /F ``` 3. Hapus folder: `%ProgramData%\GeoNetAgent` diff --git a/GeoNetAgent/agent/install/index.html b/GeoNetAgent/agent/install/index.html index 2dfddd6..13c8168 100644 --- a/GeoNetAgent/agent/install/index.html +++ b/GeoNetAgent/agent/install/index.html @@ -15,10 +15,20 @@ ol.steps { padding-left: 1.25rem; } ol.steps li { margin: .5rem 0; } .label { display: inline-block; min-width: 4.5rem; font-weight: 600; color: #52525b; } + .version-bar { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin: .75rem 0 1.25rem; } + .badge { display: inline-flex; align-items: center; gap: .4rem; background: #18181b; color: #fff; border-radius: 6px; padding: .3rem .75rem; font-size: .85rem; font-weight: 600; letter-spacing: .01em; } + .badge-label { opacity: .65; font-weight: 400; } + .badge-ver { color: #4ade80; } + .badge-date { color: #93c5fd; } + .badge-loading { opacity: .5; }

GeoNetAgent — Install & Update

+
+ versi memuat… + diperbarui memuat… +

Installer untuk laptop/PC Windows. Path install: %ProgramData%\GeoNetAgent

@@ -46,7 +56,7 @@
Harapan sukses: - Scheduled Task OK: GeoNetAgent-Report, GeoNetAgent-Report-Startup + Scheduled Task OK: GeoNetAgent-Report, GeoNetAgent-Report-Startup, GeoNetAgent-Report-Shutdown

Cek setelah install

@@ -60,6 +70,7 @@ schtasks /Query /TN GeoNetAgent-Report-Startup
  • File agent di %ProgramData%\GeoNetAgent
  • GeoNetAgent-Report-Startup — jalan saat boot (SYSTEM)
  • GeoNetAgent-Report — tiap 3 jam (SYSTEM); cek update otomatis sebelum kirim laporan
  • +
  • GeoNetAgent-Report-Shutdown — jalan saat shutdown/restart Windows (Event 1074)
  • @@ -73,5 +84,43 @@ schtasks /Query /TN GeoNetAgent-Report-Startup
  • PowerShell Admin: cd $env:TEMP
  • .\GeoNetAgent-Install.ps1 -SkipInitialRun
  • + diff --git a/GeoNetAgent/agent/install/manifest.json b/GeoNetAgent/agent/install/manifest.json index 7726551..8dd198e 100644 --- a/GeoNetAgent/agent/install/manifest.json +++ b/GeoNetAgent/agent/install/manifest.json @@ -1,5 +1,5 @@ { - "version": "0.1.10-phase0", + "version": "0.1.12-phase0", "files": [ "Send-GeoNetReport.ps1", "Collect-GeoNetReport.ps1", diff --git a/GeoNetAgent/agent/lib/Agent-InstallCore.ps1 b/GeoNetAgent/agent/lib/Agent-InstallCore.ps1 index ae5a540..abc927b 100644 --- a/GeoNetAgent/agent/lib/Agent-InstallCore.ps1 +++ b/GeoNetAgent/agent/lib/Agent-InstallCore.ps1 @@ -123,7 +123,7 @@ function Register-GeoNetAgentTask { $taskArgs = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`"" $taskCmd = "powershell.exe $taskArgs" - $taskNames = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') + $taskNames = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup', 'GeoNetAgent-Report-Shutdown') foreach ($name in $taskNames) { Unregister-ScheduledTask -TaskName $name -Confirm:$false -ErrorAction SilentlyContinue @@ -139,6 +139,58 @@ function Register-GeoNetAgentTask { '/Create', '/TN', 'GeoNetAgent-Report', '/TR', $taskCmd, '/SC', 'HOURLY', '/MO', "$Hours", '/RU', 'SYSTEM', '/RL', 'HIGHEST', '/F' ) | Out-Null + + Register-GeoNetShutdownTask -InstallRoot $InstallRoot -TaskCmd $taskCmd +} + +function Register-GeoNetShutdownTask { + param( + [string]$InstallRoot, + [string]$TaskCmd + ) + $xmlPath = Join-Path $env:TEMP 'GeoNetAgent-Shutdown-Task.xml' + $xml = @" + + + + GeoNetAgent — kirim laporan saat shutdown/restart Windows + + + + true + <QueryList><Query Id="0" Path="System"><Select Path="System">*[System[Provider[@Name='USER32'] and EventID=1074]]</Select></Query></QueryList> + + + + + S-1-5-18 + HighestAvailable + + + + IgnoreNew + false + false + PT3M + 0 + + + + powershell.exe + -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "$($InstallRoot -replace '"','')\UserAgent.ps1" -SkipAutoUpdate + + + +"@ + try { + $xml | Out-File -FilePath $xmlPath -Encoding Unicode -Force + Invoke-GeoNetSchtasks -ArgumentList @( + '/Create', '/TN', 'GeoNetAgent-Report-Shutdown', '/XML', $xmlPath, '/F' + ) | Out-Null + } + finally { + if (Test-Path $xmlPath) { Remove-Item $xmlPath -Force -ErrorAction SilentlyContinue } + } } function Set-GeoNetAgentConfig { diff --git a/GeoNetAgent/agent/lib/Get-GpuInfo.ps1 b/GeoNetAgent/agent/lib/Get-GpuInfo.ps1 index cbc5a3c..cf23037 100644 --- a/GeoNetAgent/agent/lib/Get-GpuInfo.ps1 +++ b/GeoNetAgent/agent/lib/Get-GpuInfo.ps1 @@ -18,6 +18,34 @@ function Convert-GeoNetAdapterRamMb { } } +function Get-GeoNetGpuMemoryMbFromRegistry { + param([AllowNull()][string]$PnpDeviceId) + + if (-not $PnpDeviceId) { return $null } + + try { + $baseKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}' + $subKeys = Get-ChildItem $baseKey -ErrorAction Stop | Where-Object { $_.PSChildName -match '^\d{4}$' } + + foreach ($key in $subKeys) { + $matchId = (Get-ItemProperty -Path $key.PSPath -Name 'MatchingDeviceId' -ErrorAction SilentlyContinue).'MatchingDeviceId' + + $pnpNorm = $PnpDeviceId.ToUpper() + $matchNorm = if ($matchId) { $matchId.ToUpper() } else { '' } + + if ($matchNorm -and $pnpNorm.StartsWith($matchNorm.Split('&')[0])) { + $memBytes = (Get-ItemProperty -Path $key.PSPath -Name 'HardwareInformation.MemorySize' -ErrorAction SilentlyContinue).'HardwareInformation.MemorySize' + if ($memBytes -and $memBytes -gt 0) { + $mb = [math]::Round([decimal]$memBytes / 1MB, 0) + if ($mb -gt 0 -and $mb -le 131072) { return [int]$mb } + } + } + } + } catch { + } + return $null +} + function Convert-GeoNetDriverDateIso { param($DriverDate) @@ -74,7 +102,7 @@ function Get-GeoNetGpuInfo { video_processor = Convert-GeoNetOptionalString -Value $_.VideoProcessor driver_version = Convert-GeoNetOptionalString -Value $_.DriverVersion driver_date = Convert-GeoNetDriverDateIso -DriverDate $_.DriverDate - dedicated_memory_mb = Convert-GeoNetAdapterRamMb -AdapterRam $_.AdapterRAM + dedicated_memory_mb = $(if ($pnp) { $reg = Get-GeoNetGpuMemoryMbFromRegistry -PnpDeviceId $pnp; if ($null -ne $reg) { $reg } else { Convert-GeoNetAdapterRamMb -AdapterRam $_.AdapterRAM } } else { Convert-GeoNetAdapterRamMb -AdapterRam $_.AdapterRAM }) pnp_device_id = $pnp status = Convert-GeoNetOptionalString -Value $_.Status current_resolution = $resolution diff --git a/Workspace-Context.md b/Workspace-Context.md new file mode 100644 index 0000000..bc2260f --- /dev/null +++ b/Workspace-Context.md @@ -0,0 +1,182 @@ +# Workspace Context + +## Purpose + +Dokumen ini adalah **root context** untuk seluruh workspace. + +Workspace ini memiliki satu atau lebih repository. + +**Jangan melakukan analisis seluruh workspace sekaligus.** + +Selalu identifikasi repository yang relevan, kemudian baca **`docs/context-index.md`** pada repository tersebut sebagai sumber informasi utama. + +--- + +# Rules + +1. Selalu baca `Workspace-Context.md` terlebih dahulu. +2. Tentukan repository yang relevan. +3. Baca hanya `docs/context-index.md` pada repository tersebut. +4. Ikuti seluruh referensi dokumentasi yang terdapat pada `context-index.md`. +5. Jangan membaca atau memodifikasi repository lain kecuali benar-benar dibutuhkan. +6. Jika pekerjaan melibatkan lebih dari satu repository, baca `docs/context-index.md` masing-masing repository yang terkait. +7. Jangan membuat asumsi mengenai arsitektur, API, database, server, maupun implementasi tanpa membaca dokumentasi repository terkait. + +--- + +# Repository Ownership Policy + +* Setiap repository memiliki dokumentasi, aturan, dan lifecycle masing-masing. +* Seluruh perubahan harus dilakukan pada repository yang benar. +* Jangan membuat dokumentasi lintas repository yang menyebabkan duplikasi informasi. +* Dokumentasi teknis harus tetap berada pada repository pemiliknya (Single Source of Truth). + +--- + +# Cross Repository Policy + +Apabila sebuah pekerjaan melibatkan lebih dari satu repository: + +1. Identifikasi seluruh repository yang terdampak. +2. Baca `docs/context-index.md` dari setiap repository terkait. +3. Perbarui dokumentasi pada masing-masing repository yang mengalami perubahan. +4. Dokumentasikan dependency dan integrasi antar repository apabila terjadi perubahan. +5. Pastikan perubahan tetap kompatibel dengan repository lain. + +--- + +# Knowledge Preservation Policy + +Setiap informasi yang diperoleh AI Agent selama proses: + +* Analisis +* Debugging +* Development +* Testing +* Deployment +* Production Support +* Incident Handling +* Performance Tuning +* Security Review + +yang berpotensi berguna di masa depan harus didokumentasikan pada repository yang sesuai. + +AI Agent **tidak boleh menyimpan konteks penting hanya di dalam percakapan**. + +Seluruh pengetahuan yang relevan harus dipindahkan ke dokumentasi repository agar dapat digunakan kembali oleh developer maupun AI Agent lain. + +--- + +# Documentation Policy (Mandatory) + +Seluruh repository merupakan **Single Source of Truth** untuk proses development, deployment, operasi, dan maintenance. + +Dokumentasi merupakan bagian dari source code dan memiliki prioritas yang sama dengan implementasi. + +Setiap perubahan wajib didokumentasikan. + +Minimal dokumentasi yang harus diperbarui apabila relevan: + +* Feature Development +* Enhancement +* Bug Fix +* Known Issues +* Root Cause Analysis (RCA) +* Technical Debt +* TODO +* Next Action +* ADR (Architecture Decision Record) +* Changelog +* API Documentation +* Database Structure +* Database Migration +* Environment Configuration +* Infrastructure +* Server Information +* Deployment Guide +* CI/CD +* Monitoring +* Logging +* Dependency antar service +* Breaking Changes +* Security Notes +* Workaround +* Troubleshooting +* Performance Optimization +* Operational Notes +* Catatan penting lainnya yang mempengaruhi proses development maupun deployment. + +--- + +# Documentation Quality Standard + +Dokumentasi harus: + +* Akurat +* Ringkas +* Mudah dipahami +* Selalu diperbarui +* Tidak duplikat +* Konsisten dengan implementasi kode +* Menggunakan Markdown +* Mengacu pada `docs/context-index.md` + +Apabila dokumentasi sudah tersedia, lakukan pembaruan daripada membuat file baru. + +--- + +# AI Agent Responsibilities + +Apabila AI Agent melakukan perubahan pada repository, maka AI Agent wajib: + +1. Memperbarui dokumentasi yang relevan. +2. Memperbarui `docs/context-index.md` apabila terdapat dokumen baru. +3. Memperbarui TODO apabila status berubah. +4. Memperbarui Issue apabila ditemukan bug baru. +5. Membuat ADR apabila terdapat keputusan arsitektur baru. +6. Memperbarui dokumentasi API apabila endpoint berubah. +7. Memperbarui dokumentasi database apabila schema berubah. +8. Memperbarui deployment guide apabila proses deployment berubah. +9. Memperbarui changelog untuk setiap perubahan penting. +10. Memastikan seluruh dokumentasi konsisten dengan implementasi kode. +11. Menandai dokumentasi yang sudah tidak berlaku sebagai **Deprecated**, bukan langsung menghapusnya. + +--- + +# Commit Policy + +Sebelum pekerjaan dianggap selesai: + +* Review seluruh perubahan. +* Pastikan dokumentasi telah diperbarui. +* Pastikan tidak ada secret yang ikut ter-commit. +* Pastikan repository dapat dibangun dan dijalankan apabila memungkinkan. +* Buat commit yang jelas dan deskriptif. +* Jangan melakukan commit yang mencampurkan perubahan yang tidak berkaitan. + +--- + +# Definition of Done (DoD) + +Sebuah pekerjaan **belum dianggap selesai** apabila salah satu kondisi berikut masih ada: + +* Dokumentasi belum diperbarui. +* TODO belum diperbarui. +* Issue belum diperbarui. +* API belum diperbarui. +* ADR belum dibuat (jika diperlukan). +* Changelog belum diperbarui. +* `docs/context-index.md` belum diperbarui apabila terdapat dokumen baru. + +--- + +# Notes + +* Struktur workspace bersifat dinamis. +* Repository dapat ditambah, dipindahkan, atau dihapus. +* Pastikan daftar repository pada dokumen ini selalu diperbarui. +* Seluruh dokumentasi teknis berada di masing-masing repository. +* `Workspace-Context.md` hanya berfungsi sebagai navigasi dan entry point. +* Selalu gunakan dokumentasi terbaru sebagai acuan sebelum melakukan analisis atau implementasi. +* Prioritaskan pembaruan dokumentasi yang sudah ada daripada membuat dokumentasi baru yang berpotensi duplikat. + diff --git a/anydesk.md b/anydesk.md new file mode 100644 index 0000000..4559af8 --- /dev/null +++ b/anydesk.md @@ -0,0 +1,91 @@ +# Backup AnyDesk + +Catatan sebelum uninstall AnyDesk. + +## File konfigurasi yang perlu di-backup + +| Apa | Lokasi | +|-----|--------| +| ID AnyDesk komputer ini | `C:\ProgramData\AnyDesk\service.conf` | +| Alias (jika ada) | `C:\ProgramData\AnyDesk\service.conf` | +| Daftar ID remote yang pernah dikoneksi | `C:\Users\\AppData\Roaming\AnyDesk\user.conf` | + +Baris yang perlu disimpan: +- `ad.anynet.id=...` +- `ad.anynet.alias=...` +- `ad.roster.items=...` +- `ad.roster.favorites=...` + +## Perintah untuk langsung mengekstrak ID ke file ini + +Jalankan di PowerShell: + +```powershell +$md = 'd:\Project\app on git\anydesk.md' +Add-Content $md "`n## AnyDesk ID Backup`n" +Get-Content "$env:ProgramData\AnyDesk\service.conf" | + Select-String "^(ad\.anynet\.id|ad\.anynet\.alias)=" | + Add-Content $md +Get-Content "$env:APPDATA\AnyDesk\user.conf" | + Select-String "^(ad\.roster\.items|ad\.roster\.favorites)=" | + Add-Content $md +``` + +## Catatan uninstall + +- Saat uninstaller AnyDesk menanyakan apakah ingin menghapus **configuration files**, pilih **No/Tidak** supaya ID tetap tersimpan. +- Jika ingin uninstall bersih, salin file `service.conf` dan `user.conf` dulu, lalu restore setelah install ulang. + + +## History exsisting ID Anydesk +Recent Sessions yang terlihat +| Nama/Alias | AnyDesk ID | Status | +| ------------- | ------------- | ----------------------- | +| luwsrv03@ad | 1 315 522 618 | 🟢 Online | +| pc putih | 1 586 132 192 | 🔴 Offline | +| wabotpc | 782 089 407 | 🟢 Online | +| 768 397 903 | 768 397 903 | 🟢 Online | +| 196 357 002 | 196 357 002 | 🔴 Offline | +| 1 134 767 402 | 1 134 767 402 | 🔴 Offline | +| 150 878 707 | 150 878 707 | 🔴 Offline | +| 1 837 279 788 | 1 837 279 788 | 🔴 Offline | +| 753 011 914 | 753 011 914 | 🟢 Online | +| 1 940 821 188 | 1 940 821 188 | 🟢 Online (This Device) | +| wabot | 1 087 714 174 | 🔴 Offline | +| 1 843 863 429 | 1 843 863 429 | 🔴 Offline | +| 320 578 236 | 320 578 236 | 🟢 Online | +| turen | 1 919 377 233 | 🟢 Online | +| msi | 1 404 328 337 | 🟢 Online | +| 788 800 406 | 788 800 406 | 🔴 Offline | +| 1 442 565 617 | 1 442 565 617 | 🔴 Offline | +| 492 865 875 | 492 865 875 | 🟢 Online | +| 605 743 051 | 605 743 051 | 🔴 Offline | +| 259 511 811 | 259 511 811 | 🔴 Offline | +| 120 239 635 | 120 239 635 | 🔴 Offline | +| 1 508 728 214 | 1 508 728 214 | 🔴 Offline | +| 1 835 890 522 | 1 835 890 522 | 🔴 Offline | +| 1 029 425 656 | 1 029 425 656 | 🔴 Offline | +| 1 252 927 543 | 1 252 927 543 | 🔴 Offline | +| 1 477 009 444 | 1 477 009 444 | 🟢 Online | +| 690 848 779 | 690 848 779 | 🔴 Offline | +| 652 598 394 | 652 598 394 | 🔴 Offline | +| 1 960 924 256 | 1 960 924 256 | 🔴 Offline | +| 1 049 300 050 | 1 049 300 050 | 🔴 Offline | +| 931 977 962 | 931 977 962 | 🔴 Offline | +| 1 362 964 774 | 1 362 964 774 | 🔴 Offline | +| 1 029 198 007 | 1 029 198 007 | 🔴 Offline | +| 1 817 523 476 | 1 817 523 476 | 🔴 Offline | +| 1 057 176 301 | 1 057 176 301 | 🔴 Offline | +| 1 156 489 064 | 1 156 489 064 | 🔴 Offline | +| 970 927 367 | 970 927 367 | 🔴 Offline | +| 784 419 109 | 784 419 109 | 🔴 Offline | +| 483 865 345 | 483 865 345 | 🔴 Offline | +| 1 067 787 928 | 1 067 787 928 | 🔴 Offline | +| 1 878 652 573 | 1 878 652 573 | 🔴 Offline | +| 820 781 760 | 820 781 760 | 🔴 Offline | +| 848 924 578 | 848 924 578 | 🔴 Offline | +| 728 652 074 | 728 652 074 | 🔴 Offline | +| 709 551 067 | 709 551 067 | 🟢 Online | +| Windows | 116 079 452 | 🟢 Online (Discovered) | +| lenovo | 934 784 136 | 🟢 Online (Discovered) | +| Windows | 1 205 966 377 | 🟢 Online (Discovered) | diff --git a/server-audit/deploy-log.md b/server-audit/deploy-log.md new file mode 100644 index 0000000..6445997 --- /dev/null +++ b/server-audit/deploy-log.md @@ -0,0 +1,82 @@ +# Deploy Log + +--- + +## 2026-07-01 — Feature Feedback Super Apps + +### Frontend (my.gisportal.id) +- Buat `src/components/feedback/feedback-dialog.tsx` — dialog form laporan bug/saran +- Buat `src/components/feedback/feedback-button.tsx` — floating button di semua halaman +- Update `src/app/(app)/layout.tsx` — tambah `` +- Deploy: `docker compose up -d --build` di `/opt/stacks/my-portal` + +### Backend (console.gisportal.id) +- Buat `app/Models/Feedback.php` +- Buat `app/Http/Controllers/Api/V1/FeedbackController.php` +- Buat `database/migrations/2026_07_01_000001_create_feedback_table.php` +- Update `routes/api.php` — tambah 3 route: `POST/GET /feedback`, `PATCH /feedback/{id}` +- Deploy: `docker cp` ke container `geonet-console` + `php artisan migrate --force` +- Hasil: migration `create_feedback_table` ✅ Ran, route terdaftar ✅ + +### Catatan +- Container geonet-console tidak mount source dari host (pakai image) → wajib `docker cp` untuk perubahan tanpa rebuild +- Untuk permanen: rebuild image + +--- + +## 2026-07-01 — SSH Key Setup ArcGIS Servers + +### Server 10.100.1.52 (DEV13 — ArcGIS Data Store Object) +- Install OpenSSH key via `ssh -t` dengan password sekali +- Public key: `id_ed25519_geonet_proxmox` → `C:\ProgramData\ssh\administrators_authorized_keys` +- SSH config alias: `dev13-object` +- Hasil: `ssh dev13-object` ✅ tanpa password + +### Server 10.100.1.51 (DEV11 — ArcGIS Base Development) +- Install OpenSSH key via `ssh -t` dengan password sekali +- Public key: `id_ed25519_geonet_proxmox` → `C:\ProgramData\ssh\administrators_authorized_keys` +- SSH config alias: `dev51-base` +- Hasil: `ssh dev51-base` ✅ tanpa password + +--- + +## 2026-07-01 — Disk Cleanup Server 10.100.1.24 + +- Kondisi sebelum: disk 98% (1.3 GB free / 50 GB) +- Root cause: Docker build cache 45.29 GB menumpuk +- Tindakan: `docker builder prune -af` +- Freed: 45.29 GB +- Kondisi sesudah: disk 50% (26 GB free) +- Dangling images: ~7.9 GB masih ada (aman untuk dibersihkan kapan saja) + +--- + +## 2026-06-30 — USB Passthrough VM 103 (Opr Proxmox 10.100.1.102) + +- Flashdisk Cruzer Blade terdeteksi di `usb 1-1.4` (idVendor=0781, idProduct=5567) +- Controller PCI: `0000:00:1a.0` (EHCI USB 2.0) +- Hapus passthrough per-device: `qm set 103 --delete usb1` +- Tambah passthrough controller: `qm set 103 -hostpci0 00:1a.0` +- VM 103 restart → `hostpci0: 00:1a.0` aktif +- Hasil: semua device USB yang dicolok ke bus 1 auto terdeteksi di VM 103 + +--- + +## 2026-06-30 — SSH Key Proxmox + +### Server 10.100.1.101 & 10.100.1.102 +- Key: `~/.ssh/id_ed25519_geonet_proxmox` +- SSH config alias: `proxmox` (10.100.1.101), `proxmox-opr` (10.100.1.102) +- Hasil: `ssh root@10.100.1.101` dan `ssh root@10.100.1.102` ✅ tanpa password + +--- + +## 2026-06-29 — GeoNetAgent v0.1.12-phase0 + +### Perubahan +- Tambah scheduled task `GeoNetAgent-Report-Shutdown` (Event ID 1074) +- Buat `GeoNetAgent-GPO-Shutdown.cmd` dan `GeoNetAgent-GPO-Shutdown.ps1` +- Update `GeoNetAgent-GPO-Startup.ps1` — cek task shutdown saat startup +- Bump VERSION → `0.1.12-phase0` +- Update `manifest.json` dan `index.html` (version badge dinamis) +- Deploy ke `https://agent.gisportal.id/install/` diff --git a/server-audit/host.md b/server-audit/host.md index e4f473b..6397daf 100644 --- a/server-audit/host.md +++ b/server-audit/host.md @@ -1,27 +1,67 @@ -Mikrotik : +# 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 +# Opr Proxmox + ssh root@10.100.1.102 -Reverse_proxy: + ## USB passthrough VM 103 (monitoring) + - USB bus 1 (controller PCI `0000:00:1a.0`) dipassthrough ke VM 103 + - Tujuan: flashdisk/storage docking auto terdeteksi di VM 103 + - Waktu setup: 30 Jun 2026 + - Perintah setup: + ```bash + ssh root@10.100.1.102 + qm stop 103 + qm set 103 --delete usb1 + qm set 103 -hostpci0 00:1a.0 + qm start 103 + ``` + - **Rollback** (kembalikan USB bus 1 ke Proxmox): + ```bash + ssh root@10.100.1.102 + qm stop 103 + qm set 103 --delete hostpci0 + qm start 103 + ``` + - Catatan: USB 2.0 saja (EHCI). Host kehilangan akses ke port USB bus 1. + +# Dev Proxmox + ssh root@10.100.1.101 + +# Reverse_proxy (App Server): ssh -m hmac-sha1 root@10.100.1.24 -p 22 -Main_Database: + ## Disk Cleanup — 1 Jul 2026 + - Sebelum: disk 98% penuh (1.3 GB free / 50 GB) + - Penyebab: Docker build cache menumpuk 45.29 GB + - Tindakan: `docker builder prune -af` → hapus 45.29 GB + - Hasil: disk 50% (26 GB free) + - Sisa reclaimable: ~7.9 GB dangling images + + ## Rencana Deploy Zammad (ticketing) + - Target domain: `ticket.gisportal.id` + - Stack: `/opt/stacks/zammad` + - Resource dibutuhkan: ~2–3 GB RAM, ~15 GB disk + - Status: ✅ Resource tersedia, siap deploy + - Integrasi: feedback dari Super Apps → auto-create tiket Zammad + +# Main_Database: ssh -m hmac-sha1 root@10.100.1.25 -p 22 -Main_Webserver: +# Main_Webserver: ssh -m hmac-sha1 Administrator@10.100.1.41 -p 22 -AD-LDAP: +# AD-LDAP: ssh -m hmac-sha1 Administrator@10.100.1.40 -p 22 -zabbix: +# zabbix: ssh -m hmac-sha1 root@10.100.1.42 -p 22 -Ollama (aiopr, Ubuntu 22.04) — POC & inference CPU: +# 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 @@ -34,4 +74,27 @@ Ollama (aiopr, Ubuntu 22.04) — POC & inference CPU: .\scripts\ssh-qnap.ps1 ssh -m hmac-sha1 admin@10.100.1.10 -p 22 +# ArcGIS Enterprise + +## ArcGIS Base Development + - IIS + - ArcGIS Web Adaptor + - Portal for ArcGIS + - ArcGIS Server + - ArcGIS Data Store (Relational) + ssh dev51-base + ssh Administrator@10.100.1.51 -p 22 + +## ArcGIS Data Store (Object) + - ArcGIS Data Store (Object) + ssh dev13-object + ssh Administrator@10.100.1.52 -p 22 + +## Key setup (Windows OpenSSH) + - Key: `~/.ssh/id_ed25519_geonet_proxmox` + - Public key ditambahkan ke `C:\ProgramData\ssh\administrators_authorized_keys` di masing-masing server + - Perintah setup (jalankan sekali per server): + ```powershell + ssh -t Administrator@10.100.1.51 -p 22 "powershell -Command `"Add-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDxlXfuXAb/A2YCji3XWckW37Hs7SP/yen2CQwgV5M4x proxmox-geonet'; icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r; icacls C:\ProgramData\ssh\administrators_authorized_keys /grant SYSTEM:F; icacls C:\ProgramData\ssh\administrators_authorized_keys /grant Administrators:F; Restart-Service sshd`"" + ``` diff --git a/server-connection/geonet-console/app/Http/Controllers/Api/V1/FeedbackController.php b/server-connection/geonet-console/app/Http/Controllers/Api/V1/FeedbackController.php new file mode 100644 index 0000000..9b2d599 --- /dev/null +++ b/server-connection/geonet-console/app/Http/Controllers/Api/V1/FeedbackController.php @@ -0,0 +1,99 @@ +attributes->get('api_actor'); + + $validated = $request->validate([ + 'type' => ['required', Rule::in(['bug', 'suggestion', 'other'])], + 'title' => ['required', 'string', 'max:120'], + 'message' => ['required', 'string', 'max:5000'], + 'context' => ['sometimes', 'array'], + 'context.page' => ['sometimes', 'string', 'max:500'], + 'context.url' => ['sometimes', 'string', 'max:1000'], + 'context.user_agent' => ['sometimes', 'string', 'max:500'], + 'context.reporter' => ['sometimes', 'string', 'max:100'], + 'context.reporter_name' => ['sometimes', 'string', 'max:200'], + 'context.reported_at' => ['sometimes', 'string'], + ]); + + $ctx = $validated['context'] ?? []; + + $feedback = Feedback::create([ + 'type' => $validated['type'], + 'title' => $validated['title'], + 'message' => $validated['message'], + 'status' => 'open', + 'reporter' => $ctx['reporter'] ?? ($actor?->identifier ?? null), + 'reporter_name' => $ctx['reporter_name'] ?? null, + 'page' => $ctx['page'] ?? null, + 'url' => $ctx['url'] ?? null, + 'user_agent' => $ctx['user_agent'] ?? null, + 'reported_at' => isset($ctx['reported_at']) ? now()->parse($ctx['reported_at']) : now(), + ]); + + return $this->ok([ + 'id' => $feedback->id, + 'status' => $feedback->status, + ], [], 201); + } + + public function index(Request $request): JsonResponse + { + /** @var ApiActor $actor */ + $actor = $request->attributes->get('api_actor'); + + if (!$actor?->is_admin) { + return $this->fail('Forbidden', 'forbidden', 403); + } + + $query = Feedback::query()->orderByDesc('created_at'); + + if ($request->filled('type')) { + $query->where('type', $request->query('type')); + } + if ($request->filled('status')) { + $query->where('status', $request->query('status')); + } + + $items = $query->paginate(20); + + return $this->ok($items); + } + + public function update(Request $request, int $id): JsonResponse + { + /** @var ApiActor $actor */ + $actor = $request->attributes->get('api_actor'); + + if (!$actor?->is_admin) { + return $this->fail('Forbidden', 'forbidden', 403); + } + + $feedback = Feedback::findOrFail($id); + + $validated = $request->validate([ + 'status' => ['sometimes', Rule::in(['open', 'in_progress', 'resolved', 'closed'])], + 'admin_note' => ['sometimes', 'nullable', 'string', 'max:2000'], + ]); + + $feedback->update($validated); + + return $this->ok($feedback->only(['id', 'status', 'admin_note'])); + } +} diff --git a/server-connection/geonet-console/app/Models/Feedback.php b/server-connection/geonet-console/app/Models/Feedback.php new file mode 100644 index 0000000..19989cd --- /dev/null +++ b/server-connection/geonet-console/app/Models/Feedback.php @@ -0,0 +1,26 @@ + 'datetime', + ]; +} diff --git a/server-connection/geonet-console/database/migrations/2026_07_01_000001_create_feedback_table.php b/server-connection/geonet-console/database/migrations/2026_07_01_000001_create_feedback_table.php new file mode 100644 index 0000000..391b290 --- /dev/null +++ b/server-connection/geonet-console/database/migrations/2026_07_01_000001_create_feedback_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('type', 20)->default('bug'); + $table->string('title', 120); + $table->text('message'); + $table->string('status', 20)->default('open'); + $table->string('reporter', 100)->nullable(); + $table->string('reporter_name', 200)->nullable(); + $table->string('page', 500)->nullable(); + $table->string('url', 1000)->nullable(); + $table->string('user_agent', 500)->nullable(); + $table->timestamp('reported_at')->nullable(); + $table->text('admin_note')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('feedback'); + } +}; diff --git a/server-connection/geonet-console/routes/api.php b/server-connection/geonet-console/routes/api.php index b0a7864..cf64cb2 100644 --- a/server-connection/geonet-console/routes/api.php +++ b/server-connection/geonet-console/routes/api.php @@ -2,6 +2,7 @@ use App\Http\Controllers\Api\V1\AuthController; use App\Http\Controllers\Api\V1\DatabaseController; +use App\Http\Controllers\Api\V1\FeedbackController; use App\Http\Controllers\Api\V1\LdapGroupController; use App\Http\Controllers\Api\V1\LdapUserController; use App\Http\Controllers\Api\V1\MeController; @@ -98,5 +99,14 @@ Route::prefix('v1')->group(function () { ->name('api.v1.service-tokens.active'); Route::get('/service-tokens/expired', [ServiceTokenController::class, 'getExpired']) ->name('api.v1.service-tokens.expired'); + + // Feedback — bug reports & suggestions + Route::post('/feedback', [FeedbackController::class, 'store']) + ->middleware('throttle:30,1') + ->name('api.v1.feedback.store'); + Route::get('/feedback', [FeedbackController::class, 'index']) + ->name('api.v1.feedback.index'); + Route::patch('/feedback/{id}', [FeedbackController::class, 'update']) + ->name('api.v1.feedback.update'); }); });