Skip to content

Commit d0cd515

Browse files
committed
fix: small UI fix
1 parent fbc64bd commit d0cd515

16 files changed

Lines changed: 383 additions & 109 deletions

‎.gitignore‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ iocscan.exe
1616
# Editor
1717
.idea/
1818
.vscode/
19-
*.swp
19+
*.swp
20+
21+
# go cache
22+
.gocache/

‎README.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Query indicators against VirusTotal, AbuseIPDB, ThreatFox, ipapi.is, MalwareBaza
4040
- **Domain enrichment** — VirusTotal multi-engine verdict, reputation, registrar, creation date, A records, categories, and ThreatFox C2 intelligence
4141
- **Multi-signal risk scoring** — `riskLevel` computed from manifest-driven rules across all integrations; any single signal can escalate the level independently
4242
- **Plugin architecture** — each integration is a self-contained Go file implementing a single interface; adding a new vendor requires one file and one registry line
43-
- **Web UI** — Tailwind CSS + Vue 3, authenticated scanner, generic-endpoint-backed IP/hash/domain views, column visibility toggles, export to CSV/JSON, scan history, and per-vendor cache diagnostics. The current UI keeps its established per-mode layouts while adapting generic `ScanResult` payloads locally underneath
43+
- **Web UI** — Tailwind CSS + Vue 3, authenticated scanner, generic-endpoint-backed IP/hash/domain views, column visibility toggles, export to CSV/JSON, scan history, and per-vendor cache diagnostics. The current UI keeps its established per-mode layouts while adapting generic `ScanResult` payloads locally underneath, with richer custom cards for key vendors and manifest-driven fallback cards for simple or newly added integrations
4444
- **Bulk scanning** — up to 100 IPs, hashes, or domains per request
4545
- **Local cache** — SQLite-backed caching per integration to avoid redundant API calls
4646
- **Rate limiting** — built-in token-bucket limiter with 1 MB request body cap
@@ -169,7 +169,7 @@ The `-p` flag always overrides the config file port.
169169
| Feature | Description |
170170
|---------|-------------|
171171
| Authenticated UI | Login page, per-user session, admin user management, settings page |
172-
| Cards view | Per-indicator detail cards with risk badges and source links. Benign no-hit cards are hidden; diagnostics and raw JSON still show miss/error detail |
172+
| Cards view | Per-indicator detail cards with risk badges and source links. Core vendors keep handcrafted cards where richer presentation helps, while integrations without custom UI can render automatically through manifest-driven fallback cards. Benign no-hit cards are hidden; diagnostics and raw JSON still show miss/error detail |
173173
| Table view | Sortable multi-indicator comparison table |
174174
| Column toggles | Show/hide individual fields per section |
175175
| Bulk input | Paste multiple indicators or upload a `.txt` / `.csv` file |
@@ -412,19 +412,20 @@ iocscan/
412412
├── components/
413413
│ ├── AdminPage.js — admin user management UI
414414
│ ├── ColumnDrawer.js — column visibility drawer
415+
│ ├── IntegrationCard.js — generic manifest-driven fallback card renderer
415416
│ ├── LoginPage.js — login form
416417
│ ├── IOCScanner.js — main scanner (IP, Hash, Domain tabs)
417418
│ ├── SettingsPage.js — password change + API key management
418419
│ └── ResultsTable.js — sortable results table
419420
├── composables/
420421
│ ├── useAuth.js — auth/session/page state + authenticated fetch wrapper
421422
│ ├── useColumnVisibility.js — column toggle state
422-
│ ├── genericScanResultUtils.js — shared helpers for adapting generic ScanResult payloads
423-
│ ├── useDomainResults.js — domain scan state, table, export, local generic-result adapter
424-
│ ├── useHashResults.js — hash scan state, table, export, local generic-result adapter
423+
│ ├── genericScanResultUtils.js — shared helpers for adapting generic ScanResult payloads and building manifest fallback cards
424+
│ ├── useDomainResults.js — domain scan state, table, export, local generic-result adapter + fallback card view model
425+
│ ├── useHashResults.js — hash scan state, table, export, local generic-result adapter + fallback card view model
425426
│ ├── useIntegrations.js — manifest fetch at boot
426427
│ ├── useIOCScan.js — central scan orchestration + generic endpoint calls
427-
│ ├── useIPResults.js — IP scan state, table, export, local generic-result adapter
428+
│ ├── useIPResults.js — IP scan state, table, export, local generic-result adapter + fallback card view model
428429
│ └── useScanHistory.js — scan history management
429430
├── index.html — main UI entry point
430431
├── main.js — Vue app bootstrap + loadManifests()
@@ -454,7 +455,7 @@ iocscan/
454455
455456
| File | Change |
456457
|------|--------|
457-
| `web/components/IOCScanner.js` | Usually no change for diagnostics/raw JSON; richer vendor-specific cards or table presentation may still need explicit UI work |
458+
| `web/components/IOCScanner.js` | Usually no change. Simple integrations can render automatically through manifest-driven fallback cards; only richer vendor-specific cards or bespoke table presentation still need explicit UI work |
458459
| `auth/models.go` / `auth/handlers.go` | Add encrypted per-user key storage fields if the vendor requires a new key |
459460
460461
**Additional wiring for IP integrations that require a key** (e.g. GreyNoise):
@@ -463,7 +464,7 @@ iocscan/
463464
|------|--------|
464465
| `server/server.go` | Load the stored key and pass it into the generic scan path |
465466
466-
> **Note:** The primary scanner calls generic scan endpoints and adapts generic `ScanResult` payloads locally inside the per-mode frontend composables.
467+
> **Note:** The primary scanner calls generic scan endpoints and adapts generic `ScanResult` payloads locally inside the per-mode frontend composables. If an integration has no handwritten card in `IOCScanner.js`, the frontend can still render it through `IntegrationCard.js` using the manifest card definition.
467468
468469
### Step 1 — Create `integrations/yourvendor.go`
469470

‎integrations/integration.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ type FieldDef struct {
114114
FalseLabel string `json:"falseLabel,omitempty"`
115115
TrueColor string `json:"trueColor,omitempty"`
116116
FalseColor string `json:"falseColor,omitempty"`
117+
118+
// HideFalse skips rendering this bool field when the value is false.
119+
// Useful for capability/flag style fields where only positive findings
120+
// should appear in the generic card.
121+
HideFalse bool `json:"hideFalse,omitempty"`
117122
}
118123

119124
// ── Card Definition ───────────────────────────────────────────────────────────

‎integrations/ipapi.go‎

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,40 @@ const ipapiEndpoint = "https://api.ipapi.is"
2020
// IPAPIResponse is the raw response from ipapi.is.
2121
type IPAPIResponse struct {
2222
IP string `json:"ip"`
23+
RIR string `json:"rir"`
24+
IsBogon bool `json:"is_bogon"`
25+
IsMobile bool `json:"is_mobile"`
26+
IsSatellite bool `json:"is_satellite"`
27+
IsCrawler bool `json:"is_crawler"`
28+
IsDatacenter bool `json:"is_datacenter"`
29+
IsTor bool `json:"is_tor"`
30+
IsProxy bool `json:"is_proxy"`
31+
IsVPN bool `json:"is_vpn"`
32+
IsAbuser bool `json:"is_abuser"`
33+
VPN struct {
34+
Service string `json:"service"`
35+
Type string `json:"type"`
36+
LastSeenStr string `json:"last_seen_str"`
37+
} `json:"vpn"`
2338
Company struct {
24-
Name string `json:"name"`
25-
Type string `json:"type"`
39+
Name string `json:"name"`
40+
Type string `json:"type"`
41+
AbuserScore string `json:"abuser_score"`
42+
Domain string `json:"domain"`
43+
Network string `json:"network"`
44+
Whois string `json:"whois"`
2645
} `json:"company"`
2746
ASN struct {
2847
Org string `json:"org"`
2948
} `json:"asn"`
3049
Location struct {
31-
Country string `json:"country"`
32-
State string `json:"state"`
33-
City string `json:"city"`
34-
Timezone string `json:"timezone"`
50+
Country string `json:"country"`
51+
CountryCode string `json:"country_code"`
52+
State string `json:"state"`
53+
City string `json:"city"`
54+
Timezone string `json:"timezone"`
55+
LocalTime string `json:"local_time"`
56+
IsDST bool `json:"is_dst"`
3557
} `json:"location"`
3658
}
3759

@@ -94,8 +116,13 @@ func (i IPAPIIntegration) Manifest() Manifest {
94116
{Key: "city", Label: "City", Type: FieldTypeString},
95117
{Key: "state", Label: "State", Type: FieldTypeString},
96118
{Key: "timezone", Label: "Timezone", Type: FieldTypeString},
119+
{Key: "localTime", Label: "Local Time", Type: FieldTypeString},
97120
{Key: "org", Label: "Organisation", Type: FieldTypeString},
98121
{Key: "companyName", Label: "Company", Type: FieldTypeString},
122+
{Key: "abuserScore", Label: "Abuser Score", Type: FieldTypeString},
123+
{Key: "companyDomain", Label: "Domain", Type: FieldTypeString},
124+
{Key: "vpnService", Label: "VPN Service", Type: FieldTypeString},
125+
{Key: "vpnType", Label: "VPN Type", Type: FieldTypeString},
99126
{
100127
Key: "companyType",
101128
Label: "Company Type",
@@ -110,13 +137,24 @@ func (i IPAPIIntegration) Manifest() Manifest {
110137
"education": "#94a3b8",
111138
},
112139
},
140+
{Key: "isVPN", Label: "VPN", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#f87171", FalseColor: "#94a3b8", HideFalse: true},
141+
{Key: "isProxy", Label: "Proxy", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#fb923c", FalseColor: "#94a3b8", HideFalse: true},
142+
{Key: "isTor", Label: "Tor", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#f87171", FalseColor: "#94a3b8", HideFalse: true},
143+
{Key: "isDatacenter", Label: "Datacenter", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#fb923c", FalseColor: "#94a3b8", HideFalse: true},
144+
{Key: "isCrawler", Label: "Crawler", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#fbbf24", FalseColor: "#94a3b8", HideFalse: true},
145+
{Key: "isAbuser", Label: "Abuser", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#f87171", FalseColor: "#34d399", HideFalse: true},
146+
{Key: "isBogon", Label: "Bogon", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#94a3b8", FalseColor: "#94a3b8", HideFalse: true},
147+
{Key: "isMobile", Label: "Mobile", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#60a5fa", FalseColor: "#94a3b8", HideFalse: true},
148+
{Key: "isSatellite", Label: "Satellite", Type: FieldTypeBool, TrueLabel: "Yes", FalseLabel: "No", TrueColor: "#c084fc", FalseColor: "#94a3b8", HideFalse: true},
113149
},
114150
},
115151
TableColumns: []TableColumn{
116152
{Key: "country", Label: "Country", DefaultVisible: true},
117153
{Key: "city", Label: "City", DefaultVisible: false},
118154
{Key: "org", Label: "ASN Org", DefaultVisible: true},
119155
{Key: "companyType", Label: "Company Type", DefaultVisible: true},
156+
{Key: "isVPN", Label: "VPN", DefaultVisible: false},
157+
{Key: "isAbuser", Label: "Abuser", DefaultVisible: false},
120158
},
121159
}
122160
}
@@ -148,12 +186,28 @@ func (i IPAPIIntegration) Run(ctx context.Context, ioc, apiKey string, useCache
148186

149187
func ipapiToResult(r *IPAPIResponse) *Result {
150188
return &Result{Fields: map[string]any{
151-
"country": r.Location.Country,
152-
"city": r.Location.City,
153-
"state": r.Location.State,
154-
"timezone": r.Location.Timezone,
155-
"org": r.ASN.Org,
156-
"companyName": r.Company.Name,
157-
"companyType": r.Company.Type,
189+
"country": r.Location.Country,
190+
"city": r.Location.City,
191+
"state": r.Location.State,
192+
"timezone": r.Location.Timezone,
193+
"localTime": r.Location.LocalTime,
194+
"isDST": r.Location.IsDST,
195+
"org": r.ASN.Org,
196+
"companyName": r.Company.Name,
197+
"companyType": r.Company.Type,
198+
"companyDomain": r.Company.Domain,
199+
"abuserScore": r.Company.AbuserScore,
200+
"isBogon": r.IsBogon,
201+
"isMobile": r.IsMobile,
202+
"isSatellite": r.IsSatellite,
203+
"isCrawler": r.IsCrawler,
204+
"isDatacenter": r.IsDatacenter,
205+
"isTor": r.IsTor,
206+
"isProxy": r.IsProxy,
207+
"isVPN": r.IsVPN,
208+
"isAbuser": r.IsAbuser,
209+
"vpnService": r.VPN.Service,
210+
"vpnType": r.VPN.Type,
211+
"vpnLastSeen": r.VPN.LastSeenStr,
158212
}}
159213
}

‎web/components/AppShell.js‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import IOCScanner from './IOCScanner.js';
2-
import LoginPage from './LoginPage.js';
3-
import SettingsPage from './SettingsPage.js';
4-
import AdminPage from './AdminPage.js';
1+
import IOCScanner from './IOCScanner.js?v=12';
2+
import LoginPage from './LoginPage.js?v=12';
3+
import SettingsPage from './SettingsPage.js?v=12';
4+
import AdminPage from './AdminPage.js?v=12';
55

66
import {
77
authReady,
88
currentUser,
99
currentPage,
1010
mustChangePw,
1111
checkSession,
12-
} from '../composables/useAuth.js';
12+
} from '../composables/useAuth.js?v=12';
1313

1414
const { defineComponent, computed, onMounted, h } = Vue;
1515

‎web/components/ColumnDrawer.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import {
1010
hashDynCols,
1111
toggleCol, toggleSection, toggleField,
1212
toggleHashCol, toggleHashSection, setAllCols,
13-
} from '../composables/useColumnVisibility.js';
13+
} from '../composables/useColumnVisibility.js?v=12';
1414

1515
import {
1616
currentIOCMode, colBadge, ipDrawerSections, hashDrawerSections,
1717
colDrawerOpen, openColDrawer, closeColDrawer,
18-
} from '../composables/useIOCScan.js';
18+
} from '../composables/useIOCScan.js?v=12';
1919

2020
const { defineComponent } = Vue;
2121

0 commit comments

Comments
 (0)