mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
Merge pull request #8 from Dvorinka/feat/domain-intel
Domain intel: per-record provider badges + vendor strip
This commit is contained in:
7 files changed
+884
-112
No files matched your search
@@ -6,6 +6,15 @@ All notable changes to Dash. Format follows
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Domain detail now identifies vendors per record: A/AAAA/CNAME/NS/MX/TXT
|
||||||
|
rows carry provider badges (Cloudflare, Google, Purelymail, …) with favicon
|
||||||
|
icons, plus a five-role provider strip (registrar, DNS, hosting, email, CA)
|
||||||
|
above the detail sections. Detection covers ~70 NS providers, ~45 mail
|
||||||
|
providers, ~60 CNAME targets, ASN/org matching, response-header signals,
|
||||||
|
and TXT verification/SPF attribution
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Settings dialog regrouped into icon-labelled sections (renderer, appearance,
|
- Settings dialog regrouped into icon-labelled sections (renderer, appearance,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const domainCols = `id, name, tld, active, auto_renew, alert_days_before, tags,
|
|||||||
ssl_key_size, ssl_sig_algo, ssl_alt_names, host_country, host_country_code,
|
ssl_key_size, ssl_sig_algo, ssl_alt_names, host_country, host_country_code,
|
||||||
host_region, host_city, host_isp, host_org, host_as, host_lat, host_lon,
|
host_region, host_city, host_isp, host_org, host_as, host_lat, host_lon,
|
||||||
dns_provider, email_provider, hosting_provider, ca_provider, headers,
|
dns_provider, email_provider, hosting_provider, ca_provider, headers,
|
||||||
|
records, providers,
|
||||||
favicon_url, lookup_error, interval_h, last_checked, position, alerts, created_at`
|
favicon_url, lookup_error, interval_h, last_checked, position, alerts, created_at`
|
||||||
|
|
||||||
// domainRow mirrors the domains table; JSON text columns decode on scan.
|
// domainRow mirrors the domains table; JSON text columns decode on scan.
|
||||||
@@ -82,6 +83,8 @@ type domainRow struct {
|
|||||||
HostingProvider string `json:"hostingProvider"`
|
HostingProvider string `json:"hostingProvider"`
|
||||||
CAProvider string `json:"caProvider"`
|
CAProvider string `json:"caProvider"`
|
||||||
Headers map[string]string `json:"headers"`
|
Headers map[string]string `json:"headers"`
|
||||||
|
Records []domain.DNSRecord `json:"records"`
|
||||||
|
Providers domain.Providers `json:"providers"`
|
||||||
FaviconURL string `json:"faviconUrl"`
|
FaviconURL string `json:"faviconUrl"`
|
||||||
LookupError string `json:"lookupError"`
|
LookupError string `json:"lookupError"`
|
||||||
IntervalH int `json:"intervalH"`
|
IntervalH int `json:"intervalH"`
|
||||||
@@ -93,7 +96,7 @@ type domainRow struct {
|
|||||||
|
|
||||||
func scanDomain(row interface{ Scan(...any) error }) (*domainRow, error) {
|
func scanDomain(row interface{ Scan(...any) error }) (*domainRow, error) {
|
||||||
var d domainRow
|
var d domainRow
|
||||||
var tags, statuses, ns, mx, txt, v4, v6, altNames, headers, alerts string
|
var tags, statuses, ns, mx, txt, v4, v6, altNames, headers, records, providers, alerts string
|
||||||
var active, autoRenew, privacy, lock int
|
var active, autoRenew, privacy, lock int
|
||||||
err := row.Scan(&d.ID, &d.Name, &d.TLD, &active, &autoRenew, &d.AlertDaysBefore,
|
err := row.Scan(&d.ID, &d.Name, &d.TLD, &active, &autoRenew, &d.AlertDaysBefore,
|
||||||
&tags, &d.Notes, &d.ExpiryDate, &d.CreationDate, &d.UpdatedDate,
|
&tags, &d.Notes, &d.ExpiryDate, &d.CreationDate, &d.UpdatedDate,
|
||||||
@@ -104,7 +107,7 @@ func scanDomain(row interface{ Scan(...any) error }) (*domainRow, error) {
|
|||||||
&d.SSLKeySize, &d.SSLSigAlgo, &altNames, &d.HostCountry, &d.HostCountryCode,
|
&d.SSLKeySize, &d.SSLSigAlgo, &altNames, &d.HostCountry, &d.HostCountryCode,
|
||||||
&d.HostRegion, &d.HostCity, &d.HostISP, &d.HostOrg, &d.HostAS, &d.HostLat,
|
&d.HostRegion, &d.HostCity, &d.HostISP, &d.HostOrg, &d.HostAS, &d.HostLat,
|
||||||
&d.HostLon, &d.DNSProvider, &d.EmailProvider, &d.HostingProvider, &d.CAProvider,
|
&d.HostLon, &d.DNSProvider, &d.EmailProvider, &d.HostingProvider, &d.CAProvider,
|
||||||
&headers, &d.FaviconURL, &d.LookupError, &d.IntervalH, &d.LastChecked,
|
&headers, &records, &providers, &d.FaviconURL, &d.LookupError, &d.IntervalH, &d.LastChecked,
|
||||||
&d.Position, &alerts, &d.CreatedAt)
|
&d.Position, &alerts, &d.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -124,6 +127,9 @@ func scanDomain(row interface{ Scan(...any) error }) (*domainRow, error) {
|
|||||||
d.SSLAltNames = decodeList(altNames)
|
d.SSLAltNames = decodeList(altNames)
|
||||||
d.Headers = map[string]string{}
|
d.Headers = map[string]string{}
|
||||||
_ = json.Unmarshal([]byte(headers), &d.Headers)
|
_ = json.Unmarshal([]byte(headers), &d.Headers)
|
||||||
|
d.Records = []domain.DNSRecord{}
|
||||||
|
_ = json.Unmarshal([]byte(records), &d.Records)
|
||||||
|
_ = json.Unmarshal([]byte(providers), &d.Providers)
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,6 +423,8 @@ func (s *Server) applyResult(id string, r *domain.Result) {
|
|||||||
expiry, created, updated = fmtT(r.ExpiryDate), fmtT(r.CreationDate), fmtT(r.UpdatedDate)
|
expiry, created, updated = fmtT(r.ExpiryDate), fmtT(r.CreationDate), fmtT(r.UpdatedDate)
|
||||||
sslFrom, sslTo = fmtT(r.SSLValidFrom), fmtT(r.SSLValidTo)
|
sslFrom, sslTo = fmtT(r.SSLValidFrom), fmtT(r.SSLValidTo)
|
||||||
hdr, _ := json.Marshal(r.Headers)
|
hdr, _ := json.Marshal(r.Headers)
|
||||||
|
recs, _ := json.Marshal(r.Records)
|
||||||
|
provs, _ := json.Marshal(r.Providers)
|
||||||
|
|
||||||
_, err := s.db.Exec(`UPDATE domains SET
|
_, err := s.db.Exec(`UPDATE domains SET
|
||||||
expiry_date=?, creation_date=?, updated_date=?, registrar_name=?,
|
expiry_date=?, creation_date=?, updated_date=?, registrar_name=?,
|
||||||
@@ -428,7 +436,8 @@ func (s *Server) applyResult(id string, r *domain.Result) {
|
|||||||
ssl_key_size=?, ssl_sig_algo=?, ssl_alt_names=?, host_country=?,
|
ssl_key_size=?, ssl_sig_algo=?, ssl_alt_names=?, host_country=?,
|
||||||
host_country_code=?, host_region=?, host_city=?, host_isp=?, host_org=?,
|
host_country_code=?, host_region=?, host_city=?, host_isp=?, host_org=?,
|
||||||
host_as=?, host_lat=?, host_lon=?, dns_provider=?, email_provider=?,
|
host_as=?, host_lat=?, host_lon=?, dns_provider=?, email_provider=?,
|
||||||
hosting_provider=?, ca_provider=?, headers=?, favicon_url=?,
|
hosting_provider=?, ca_provider=?, headers=?, records=?, providers=?,
|
||||||
|
favicon_url=?,
|
||||||
lookup_error=?, last_checked=strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
lookup_error=?, last_checked=strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
WHERE id=?`,
|
WHERE id=?`,
|
||||||
expiry, created, updated, r.RegistrarName, r.RegistrarID, r.RegistrarURL,
|
expiry, created, updated, r.RegistrarName, r.RegistrarID, r.RegistrarURL,
|
||||||
@@ -440,7 +449,7 @@ func (s *Server) applyResult(id string, r *domain.Result) {
|
|||||||
enc(r.SSLAltNames), r.HostCountry, r.HostCountryCode, r.HostRegion,
|
enc(r.SSLAltNames), r.HostCountry, r.HostCountryCode, r.HostRegion,
|
||||||
r.HostCity, r.HostISP, r.HostOrg, r.HostAS, r.HostLat, r.HostLon,
|
r.HostCity, r.HostISP, r.HostOrg, r.HostAS, r.HostLat, r.HostLon,
|
||||||
r.DNSProvider, r.EmailProvider, r.HostingProvider, r.CAProvider,
|
r.DNSProvider, r.EmailProvider, r.HostingProvider, r.CAProvider,
|
||||||
string(hdr), r.FaviconURL, truncate(r.Error, 300), id)
|
string(hdr), string(recs), string(provs), r.FaviconURL, truncate(r.Error, 300), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("domain apply", zap.Error(err))
|
s.log.Error("domain apply", zap.Error(err))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- Structured DNS records + provider identifications for the domain detail
|
||||||
|
-- page. JSON blobs — the flat columns stay for compat and check history.
|
||||||
|
ALTER TABLE domains ADD COLUMN records TEXT NOT NULL DEFAULT '[]';
|
||||||
|
ALTER TABLE domains ADD COLUMN providers TEXT NOT NULL DEFAULT '{}';
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
ALTER TABLE domains DROP COLUMN providers;
|
||||||
|
ALTER TABLE domains DROP COLUMN records;
|
||||||
@@ -88,9 +88,46 @@ type Result struct {
|
|||||||
HostingProvider string `json:"hostingProvider"`
|
HostingProvider string `json:"hostingProvider"`
|
||||||
CAProvider string `json:"caProvider"`
|
CAProvider string `json:"caProvider"`
|
||||||
|
|
||||||
|
// Structured intel — domainstack-style record list + vendor strip.
|
||||||
|
Records []DNSRecord `json:"records"`
|
||||||
|
Providers Providers `json:"providers"`
|
||||||
|
|
||||||
Headers map[string]string `json:"headers"`
|
Headers map[string]string `json:"headers"`
|
||||||
FaviconURL string `json:"faviconUrl"`
|
FaviconURL string `json:"faviconUrl"`
|
||||||
Error string `json:"error"` // non-fatal partial failure note
|
Error string `json:"error"` // non-fatal partial failure note
|
||||||
|
|
||||||
|
mxPairs []mxPair // structured MX; MXRecords keeps the display string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider names the vendor behind a record or role. Domain feeds the
|
||||||
|
// favicon icon rendered next to the name in the UI.
|
||||||
|
type Provider struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Providers is the detected-vendor strip: registrar, DNS host, hosting,
|
||||||
|
// email, and certificate authority.
|
||||||
|
type Providers struct {
|
||||||
|
Registrar *Provider `json:"registrar,omitempty"`
|
||||||
|
DNS *Provider `json:"dns,omitempty"`
|
||||||
|
Email *Provider `json:"email,omitempty"`
|
||||||
|
Hosting *Provider `json:"hosting,omitempty"`
|
||||||
|
CA *Provider `json:"ca,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSRecord is one resolved record with the vendor its value points at.
|
||||||
|
type DNSRecord struct {
|
||||||
|
Type string `json:"type"` // A AAAA CNAME NS MX TXT
|
||||||
|
Value string `json:"value"`
|
||||||
|
Priority *int `json:"priority,omitempty"` // MX preference
|
||||||
|
Provider *Provider `json:"provider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mxPair struct {
|
||||||
|
host string
|
||||||
|
pref int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean normalises user input to a bare registrable hostname.
|
// Clean normalises user input to a bare registrable hostname.
|
||||||
@@ -125,6 +162,7 @@ func (l *Lookup) Run(ctx context.Context, rawName string) *Result {
|
|||||||
IPv4: []string{},
|
IPv4: []string{},
|
||||||
IPv6: []string{},
|
IPv6: []string{},
|
||||||
SSLAltNames: []string{},
|
SSLAltNames: []string{},
|
||||||
|
Records: []DNSRecord{},
|
||||||
Headers: map[string]string{},
|
Headers: map[string]string{},
|
||||||
FaviconURL: fmt.Sprintf("https://www.google.com/s2/favicons?domain=%s&sz=128", name),
|
FaviconURL: fmt.Sprintf("https://www.google.com/s2/favicons?domain=%s&sz=128", name),
|
||||||
}
|
}
|
||||||
@@ -155,14 +193,68 @@ func (l *Lookup) Run(ctx context.Context, rawName string) *Result {
|
|||||||
l.host(r.IPv4[0], r)
|
l.host(r.IPv4[0], r)
|
||||||
}
|
}
|
||||||
l.headers(ctx, name, r)
|
l.headers(ctx, name, r)
|
||||||
|
l.identify(r)
|
||||||
r.DNSProvider = detectDNSProvider(r.NameServers)
|
|
||||||
r.EmailProvider = detectEmailProvider(r.MXRecords)
|
|
||||||
r.HostingProvider = detectHostingProvider(r.Headers)
|
|
||||||
r.CAProvider = detectCA(r.SSLIssuer)
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// identify resolves provider vendors after every probe has run, then builds
|
||||||
|
// the structured record list the detail page renders.
|
||||||
|
func (l *Lookup) identify(r *Result) {
|
||||||
|
r.Providers.Registrar = matchProvider(r.RegistrarName, registrarProviders)
|
||||||
|
for _, n := range r.NameServers {
|
||||||
|
if p := matchProvider(n, nsProviders); p != nil {
|
||||||
|
r.Providers.DNS = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range r.mxPairs {
|
||||||
|
if p := matchProvider(m.host, mailProviders); p != nil {
|
||||||
|
r.Providers.Email = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Providers.Hosting = detectHostProvider(r)
|
||||||
|
r.Providers.CA = matchProvider(r.SSLIssuer, caProviders)
|
||||||
|
// Legacy flat names mirror the strip for the list page + old clients.
|
||||||
|
r.DNSProvider = nameOf(r.Providers.DNS)
|
||||||
|
r.EmailProvider = nameOf(r.Providers.Email)
|
||||||
|
r.HostingProvider = nameOf(r.Providers.Hosting)
|
||||||
|
r.CAProvider = nameOf(r.Providers.CA)
|
||||||
|
|
||||||
|
recs := make([]DNSRecord, 0,
|
||||||
|
len(r.IPv4)+len(r.IPv6)+len(r.NameServers)+len(r.mxPairs)+len(r.TXTRecords)+1)
|
||||||
|
add := func(t, v string, pri *int, p *Provider) {
|
||||||
|
recs = append(recs, DNSRecord{Type: t, Value: v, Priority: pri, Provider: p})
|
||||||
|
}
|
||||||
|
for _, ip := range r.IPv4 {
|
||||||
|
add("A", ip, nil, r.Providers.Hosting)
|
||||||
|
}
|
||||||
|
for _, ip := range r.IPv6 {
|
||||||
|
add("AAAA", ip, nil, r.Providers.Hosting)
|
||||||
|
}
|
||||||
|
if r.CNAME != "" {
|
||||||
|
add("CNAME", r.CNAME, nil, matchProvider(r.CNAME, hostTargets))
|
||||||
|
}
|
||||||
|
for _, n := range r.NameServers {
|
||||||
|
add("NS", n, nil, matchProvider(n, nsProviders))
|
||||||
|
}
|
||||||
|
for _, m := range r.mxPairs {
|
||||||
|
pri := m.pref
|
||||||
|
add("MX", m.host, &pri, matchProvider(m.host, mailProviders))
|
||||||
|
}
|
||||||
|
for _, t := range r.TXTRecords {
|
||||||
|
add("TXT", t, nil, matchTXTProvider(t))
|
||||||
|
}
|
||||||
|
r.Records = recs
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameOf(p *Provider) string {
|
||||||
|
if p == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.Name
|
||||||
|
}
|
||||||
|
|
||||||
// --- DNS --------------------------------------------------------------------
|
// --- DNS --------------------------------------------------------------------
|
||||||
|
|
||||||
func (l *Lookup) dns(ctx context.Context, name string, r *Result) {
|
func (l *Lookup) dns(ctx context.Context, name string, r *Result) {
|
||||||
@@ -174,7 +266,9 @@ func (l *Lookup) dns(ctx context.Context, name string, r *Result) {
|
|||||||
}
|
}
|
||||||
if mx, err := res.LookupMX(ctx, name); err == nil {
|
if mx, err := res.LookupMX(ctx, name); err == nil {
|
||||||
for _, m := range mx {
|
for _, m := range mx {
|
||||||
r.MXRecords = append(r.MXRecords, fmt.Sprintf("%s (pri %d)", strings.TrimSuffix(m.Host, "."), m.Pref))
|
host := strings.TrimSuffix(strings.ToLower(m.Host), ".")
|
||||||
|
r.mxPairs = append(r.mxPairs, mxPair{host: host, pref: int(m.Pref)})
|
||||||
|
r.MXRecords = append(r.MXRecords, fmt.Sprintf("%s (pri %d)", host, m.Pref))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if txt, err := res.LookupTXT(ctx, name); err == nil {
|
if txt, err := res.LookupTXT(ctx, name); err == nil {
|
||||||
@@ -297,108 +391,639 @@ func (l *Lookup) headers(ctx context.Context, name string, r *Result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Provider detection (condensed port of beszel's detect package) -----------
|
// --- Provider detection (condensed port of beszel's detect package) -----------
|
||||||
|
//
|
||||||
|
// Tables map a substring of the record value / org / issuer to a vendor name
|
||||||
|
// plus the brand domain used for the favicon icon. Order matters — first
|
||||||
|
// match wins, so specific substrings precede generic ones.
|
||||||
|
|
||||||
func detectDNSProvider(ns []string) string {
|
type provMatch struct{ sub, name, dom string }
|
||||||
table := []struct{ sub, name string }{
|
|
||||||
{"cloudflare", "Cloudflare"}, {"awsdns", "Amazon Route 53"},
|
func prov(name, dom string) *Provider {
|
||||||
{"googledomains", "Google Domains"}, {"google.com", "Google Domains"},
|
return &Provider{Name: name, Domain: dom,
|
||||||
{"namecheap", "Namecheap"}, {"godaddy", "GoDaddy"}, {"domaincontrol", "GoDaddy"},
|
Icon: fmt.Sprintf("https://www.google.com/s2/favicons?domain=%s&sz=64", dom)}
|
||||||
{"nsone.net", "NS1"}, {"digitalocean", "DigitalOcean"}, {"linode", "Linode"},
|
|
||||||
{"vultr", "Vultr"}, {"he.net", "Hurricane Electric"}, {"dynect", "Dyn (Oracle)"},
|
|
||||||
{"ultradns", "UltraDNS"}, {"dnsimple", "DNSimple"}, {"hover", "Hover"},
|
|
||||||
{"enom", "eNom"}, {"worldnic", "Network Solutions"}, {"zoneedit", "ZoneEdit"},
|
|
||||||
{"easydns", "EasyDNS"}, {"gandi", "Gandi"}, {"ovh", "OVH"},
|
|
||||||
{"hetzner", "Hetzner"}, {"azure-dns", "Microsoft Azure"},
|
|
||||||
{"dns.google", "Google Cloud DNS"}, {"porkbun", "Porkbun"},
|
|
||||||
{"cloudns", "ClouDNS"}, {"desec", "deSEC"}, {"nic.cz", "CZ.NIC"},
|
|
||||||
}
|
}
|
||||||
for _, n := range ns {
|
|
||||||
n = strings.ToLower(n)
|
func matchProvider(haystack string, table []provMatch) *Provider {
|
||||||
|
h := strings.ToLower(haystack)
|
||||||
for _, t := range table {
|
for _, t := range table {
|
||||||
if strings.Contains(n, t.sub) {
|
if strings.Contains(h, t.sub) {
|
||||||
return t.name
|
return prov(t.name, t.dom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectEmailProvider(mx []string) string {
|
var nsProviders = []provMatch{
|
||||||
table := []struct{ sub, name string }{
|
{"cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
{"google.com", "Google Workspace"}, {"aspmx", "Google Workspace"},
|
{"awsdns", "Amazon Route 53", "aws.amazon.com"},
|
||||||
{"outlook.com", "Microsoft 365"}, {"protection.outlook", "Microsoft 365"},
|
{"googledomains", "Google Domains", "domains.google"},
|
||||||
{"mail.protection", "Microsoft 365"}, {"protonmail", "Proton Mail"},
|
{"dns.google", "Google Cloud DNS", "cloud.google.com"},
|
||||||
{"proton.me", "Proton Mail"}, {"zoho", "Zoho Mail"},
|
{"azure-dns", "Microsoft Azure", "azure.microsoft.com"},
|
||||||
{"fastmail", "Fastmail"}, {"mimecast", "Mimecast"},
|
{"domaincontrol", "GoDaddy", "godaddy.com"},
|
||||||
{"proofpoint", "Proofpoint"}, {"barracuda", "Barracuda"},
|
{"godaddy", "GoDaddy", "godaddy.com"},
|
||||||
{"mailgun", "Mailgun"}, {"sendgrid", "SendGrid"},
|
{"registrar-servers", "Namecheap", "namecheap.com"},
|
||||||
{"amazonses", "Amazon SES"}, {"yandex", "Yandex"},
|
{"namecheap", "Namecheap", "namecheap.com"},
|
||||||
{"tuta", "Tuta"}, {"mailbox.org", "mailbox.org"},
|
{"nsone.net", "NS1", "ns1.com"},
|
||||||
}
|
{"digitalocean", "DigitalOcean", "digitalocean.com"},
|
||||||
for _, m := range mx {
|
{"linode", "Akamai", "akamai.com"},
|
||||||
m = strings.ToLower(m)
|
{"akamaitech", "Akamai", "akamai.com"},
|
||||||
for _, t := range table {
|
{"vultr", "Vultr", "vultr.com"},
|
||||||
if strings.Contains(m, t.sub) {
|
{"he.net", "Hurricane Electric", "he.net"},
|
||||||
return t.name
|
{"dynect", "Dyn (Oracle)", "dyn.com"},
|
||||||
}
|
{"ultradns", "UltraDNS", "verisign.com"},
|
||||||
}
|
{"dnsimple", "DNSimple", "dnsimple.com"},
|
||||||
}
|
{"hover", "Hover", "hover.com"},
|
||||||
return ""
|
{"enom", "eNom", "enom.com"},
|
||||||
|
{"worldnic", "Network Solutions", "networksolutions.com"},
|
||||||
|
{"zoneedit", "ZoneEdit", "zoneedit.com"},
|
||||||
|
{"easydns", "EasyDNS", "easydns.com"},
|
||||||
|
{"gandi", "Gandi", "gandi.net"},
|
||||||
|
{"ovh", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"hetzner", "Hetzner", "hetzner.com"},
|
||||||
|
{"your-server.de", "Hetzner", "hetzner.com"},
|
||||||
|
{"porkbun", "Porkbun", "porkbun.com"},
|
||||||
|
{"cloudns", "ClouDNS", "cloudns.net"},
|
||||||
|
{"desec", "deSEC", "desec.io"},
|
||||||
|
{"nic.cz", "CZ.NIC", "nic.cz"},
|
||||||
|
{"dnsmadeeasy", "DNS Made Easy", "dnsmadeeasy.com"},
|
||||||
|
{"ui-dns", "IONOS", "ionos.com"},
|
||||||
|
{"squarespacedns", "Squarespace", "squarespace.com"},
|
||||||
|
{"wordpress.com", "WordPress.com", "wordpress.com"},
|
||||||
|
{"dns-parking.com", "Hostinger", "hostinger.com"},
|
||||||
|
{"hostinger", "Hostinger", "hostinger.com"},
|
||||||
|
{"siteground", "SiteGround", "siteground.com"},
|
||||||
|
{"dreamhost", "DreamHost", "dreamhost.com"},
|
||||||
|
{"inmotionhosting", "InMotion", "inmotionhosting.com"},
|
||||||
|
{"bluehost", "Bluehost", "bluehost.com"},
|
||||||
|
{"hostgator", "HostGator", "hostgator.com"},
|
||||||
|
{"kinstadns", "Kinsta", "kinsta.com"},
|
||||||
|
{"kasserver", "ALL-INKL.COM", "all-inkl.com"},
|
||||||
|
{"all-inkl", "ALL-INKL.COM", "all-inkl.com"},
|
||||||
|
{"strato", "STRATO", "strato.de"},
|
||||||
|
{"udagdns", "United Domains", "uniteddomains.com"},
|
||||||
|
{"udag.de", "United Domains", "uniteddomains.com"},
|
||||||
|
{"transip", "TransIP", "transip.nl"},
|
||||||
|
{"infomaniak", "Infomaniak", "infomaniak.com"},
|
||||||
|
{"one.com", "One.com", "one.com"},
|
||||||
|
{"aruba", "Aruba", "aruba.it"},
|
||||||
|
{"technorail", "Aruba", "aruba.it"},
|
||||||
|
{"inwx", "INWX", "inwx.com"},
|
||||||
|
{"kasserver.com", "ALL-INKL.COM", "all-inkl.com"},
|
||||||
|
{"contabo", "Contabo", "contabo.com"},
|
||||||
|
{"netcup", "netcup", "netcup.eu"},
|
||||||
|
{"glesys", "GleSYS", "glesys.se"},
|
||||||
|
{"scaleway", "Scaleway", "scaleway.com"},
|
||||||
|
{"exoscale", "Exoscale", "exoscale.com"},
|
||||||
|
{"dnspod", "DNSPod", "dnspod.com"},
|
||||||
|
{"hicloud", "Huawei Cloud", "huaweicloud.com"},
|
||||||
|
{"alidns", "Alibaba Cloud", "alibabacloud.com"},
|
||||||
|
{"purelymail", "Purelymail", "purelymail.com"},
|
||||||
|
{"migadu", "Migadu", "migadu.com"},
|
||||||
|
{"improvmx", "ImprovMX", "improvmx.com"},
|
||||||
|
{"bunny", "Bunny", "bunny.net"},
|
||||||
|
{"edgekey", "Akamai", "akamai.com"},
|
||||||
|
{"edgesuite", "Akamai", "akamai.com"},
|
||||||
|
{"fastly", "Fastly", "fastly.com"},
|
||||||
|
{"cdn77", "CDN77", "cdn77.com"},
|
||||||
|
{"stackpath", "StackPath", "stackpath.com"},
|
||||||
|
{"ns.cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
|
{"vercel-dns", "Vercel", "vercel.com"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectHostingProvider(h map[string]string) string {
|
var mailProviders = []provMatch{
|
||||||
|
{"aspmx", "Google Workspace", "google.com"},
|
||||||
|
{"googlemail", "Google Workspace", "google.com"},
|
||||||
|
{"google.com", "Google Workspace", "google.com"},
|
||||||
|
{"outlook.com", "Microsoft 365", "microsoft.com"},
|
||||||
|
{"protection.outlook", "Microsoft 365", "microsoft.com"},
|
||||||
|
{"mail.protection", "Microsoft 365", "microsoft.com"},
|
||||||
|
{"protonmail", "Proton Mail", "proton.me"},
|
||||||
|
{"proton.me", "Proton Mail", "proton.me"},
|
||||||
|
{"zoho", "Zoho Mail", "zoho.com"},
|
||||||
|
{"fastmail", "Fastmail", "fastmail.com"},
|
||||||
|
{"messagingengine", "Fastmail", "fastmail.com"},
|
||||||
|
{"mimecast", "Mimecast", "mimecast.com"},
|
||||||
|
{"proofpoint", "Proofpoint", "proofpoint.com"},
|
||||||
|
{"barracuda", "Barracuda", "barracuda.com"},
|
||||||
|
{"mailgun", "Mailgun", "mailgun.com"},
|
||||||
|
{"sendgrid", "SendGrid", "sendgrid.com"},
|
||||||
|
{"amazonses", "Amazon SES", "aws.amazon.com"},
|
||||||
|
{"yandex", "Yandex", "yandex.com"},
|
||||||
|
{"tuta", "Tuta", "tuta.com"},
|
||||||
|
{"mailbox.org", "mailbox.org", "mailbox.org"},
|
||||||
|
{"purelymail", "Purelymail", "purelymail.com"},
|
||||||
|
{"migadu", "Migadu", "migadu.com"},
|
||||||
|
{"mxroute", "MXroute", "mxroute.com"},
|
||||||
|
{"mymxroute", "MXroute", "mxroute.com"},
|
||||||
|
{"icloud.com", "iCloud Mail", "apple.com"},
|
||||||
|
{"infomaniak", "Infomaniak", "infomaniak.com"},
|
||||||
|
{"mailpod", "One.com", "one.com"},
|
||||||
|
{"one.com", "One.com", "one.com"},
|
||||||
|
{"gmx", "GMX", "gmx.net"},
|
||||||
|
{"forwardemail", "Forward Email", "forwardemail.net"},
|
||||||
|
{"posteo", "Posteo", "posteo.de"},
|
||||||
|
{"mailo", "Mailo", "mailo.com"},
|
||||||
|
{"hey.com", "HEY", "hey.com"},
|
||||||
|
{"privateemail", "Namecheap Private Email", "namecheap.com"},
|
||||||
|
{"registrar-servers", "Namecheap", "namecheap.com"},
|
||||||
|
{"ovh.net", "OVHcloud Mail", "ovhcloud.com"},
|
||||||
|
{"perfora.net", "IONOS Mail", "ionos.com"},
|
||||||
|
{"ui-dns", "IONOS", "ionos.com"},
|
||||||
|
{"improvmx", "ImprovMX", "improvmx.com"},
|
||||||
|
{"porkbun", "Porkbun Mail", "porkbun.com"},
|
||||||
|
{"kundenserver", "IONOS Mail", "ionos.com"},
|
||||||
|
{"t-online", "T-Online", "telekom.de"},
|
||||||
|
{"web.de", "WEB.DE", "web.de"},
|
||||||
|
{"mail.ru", "Mail.ru", "mail.ru"},
|
||||||
|
{"qq.com", "QQ Mail", "qq.com"},
|
||||||
|
{"163.com", "NetEase Mail", "163.com"},
|
||||||
|
{"exim.rs", "Exim", "exim.org"},
|
||||||
|
{"mx.cloudflare", "Cloudflare Email Routing", "cloudflare.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostTargets: CNAME targets that reveal the hosting platform.
|
||||||
|
var hostTargets = []provMatch{
|
||||||
|
{"github.io", "GitHub Pages", "github.com"},
|
||||||
|
{"vercel-dns", "Vercel", "vercel.com"},
|
||||||
|
{"vercel.app", "Vercel", "vercel.com"},
|
||||||
|
{"netlify", "Netlify", "netlify.com"},
|
||||||
|
{"cloudfront.net", "AWS CloudFront", "aws.amazon.com"},
|
||||||
|
{"azurefd.net", "Azure Front Door", "azure.microsoft.com"},
|
||||||
|
{"azureedge.net", "Azure CDN", "azure.microsoft.com"},
|
||||||
|
{"cloudapp.net", "Microsoft Azure", "azure.microsoft.com"},
|
||||||
|
{"trafficmanager.net", "Microsoft Azure", "azure.microsoft.com"},
|
||||||
|
{"herokuapp.com", "Heroku", "heroku.com"},
|
||||||
|
{"herokudns.com", "Heroku", "heroku.com"},
|
||||||
|
{"workers.dev", "Cloudflare Workers", "cloudflare.com"},
|
||||||
|
{"pages.dev", "Cloudflare Pages", "cloudflare.com"},
|
||||||
|
{"cdn.cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
|
{"fly.dev", "Fly.io", "fly.io"},
|
||||||
|
{"onrender.com", "Render", "render.com"},
|
||||||
|
{"railway.app", "Railway", "railway.app"},
|
||||||
|
{"surge.sh", "Surge", "surge.sh"},
|
||||||
|
{"neocities.org", "Neocities", "neocities.org"},
|
||||||
|
{"readthedocs", "Read the Docs", "readthedocs.io"},
|
||||||
|
{"webflow.io", "Webflow", "webflow.com"},
|
||||||
|
{"squarespace.com", "Squarespace", "squarespace.com"},
|
||||||
|
{"wixdns.net", "Wix", "wix.com"},
|
||||||
|
{"wixsite.com", "Wix", "wix.com"},
|
||||||
|
{"editorx.com", "Wix Studio", "wix.com"},
|
||||||
|
{"myshopify.com", "Shopify", "shopify.com"},
|
||||||
|
{"wpengine", "WP Engine", "wpengine.com"},
|
||||||
|
{"wordpress.com", "WordPress.com", "wordpress.com"},
|
||||||
|
{"googlehosted.com", "Google", "google.com"},
|
||||||
|
{"googleapis.com", "Google Cloud", "cloud.google.com"},
|
||||||
|
{"fastly.net", "Fastly", "fastly.com"},
|
||||||
|
{"fastlylb.net", "Fastly", "fastly.com"},
|
||||||
|
{"akamaiedge.net", "Akamai", "akamai.com"},
|
||||||
|
{"edgekey.net", "Akamai", "akamai.com"},
|
||||||
|
{"edgesuite.net", "Akamai", "akamai.com"},
|
||||||
|
{"akamai.net", "Akamai", "akamai.com"},
|
||||||
|
{"b-cdn.net", "Bunny", "bunny.net"},
|
||||||
|
{"bunnycdn", "Bunny", "bunny.net"},
|
||||||
|
{"elb.amazonaws.com", "AWS ELB", "aws.amazon.com"},
|
||||||
|
{"amazonaws.com", "Amazon AWS", "aws.amazon.com"},
|
||||||
|
{"stackpathdns", "StackPath", "stackpath.com"},
|
||||||
|
{"kinsta.cloud", "Kinsta", "kinsta.com"},
|
||||||
|
{"pantheonsite.io", "Pantheon", "pantheon.io"},
|
||||||
|
{"pantheon.io", "Pantheon", "pantheon.io"},
|
||||||
|
{"ghost.io", "Ghost", "ghost.org"},
|
||||||
|
{"hubspot", "HubSpot", "hubspot.com"},
|
||||||
|
{"dnsimple.com", "DNSimple", "dnsimple.com"},
|
||||||
|
{"pressdns.com", "Pressable", "pressable.com"},
|
||||||
|
{"kinsta", "Kinsta", "kinsta.com"},
|
||||||
|
{"cdn77", "CDN77", "cdn77.com"},
|
||||||
|
{"incapsula", "Imperva", "imperva.com"},
|
||||||
|
{"sucuri", "Sucuri", "sucuri.net"},
|
||||||
|
{"flywheel", "Flywheel", "getflywheel.com"},
|
||||||
|
{"gridscale", "gridscale", "gridscale.io"},
|
||||||
|
{"webflow", "Webflow", "webflow.com"},
|
||||||
|
{"framer.app", "Framer", "framer.com"},
|
||||||
|
{"carrd.co", "Carrd", "carrd.co"},
|
||||||
|
{"super.so", "Super", "super.so"},
|
||||||
|
{"typedream.app", "Typedream", "typedream.com"},
|
||||||
|
{"cargo.site", "Cargo", "cargo.site"},
|
||||||
|
{"gitlab.io", "GitLab Pages", "gitlab.com"},
|
||||||
|
{"bitbucket.io", "Bitbucket", "bitbucket.org"},
|
||||||
|
{"codeberg.page", "Codeberg Pages", "codeberg.org"},
|
||||||
|
{"glitch.me", "Glitch", "glitch.com"},
|
||||||
|
{"repl.co", "Replit", "replit.com"},
|
||||||
|
{"replit.app", "Replit", "replit.com"},
|
||||||
|
{"deno.dev", "Deno Deploy", "deno.com"},
|
||||||
|
{"supabase.co", "Supabase", "supabase.com"},
|
||||||
|
{"firebaseapp.com", "Firebase", "firebase.google.com"},
|
||||||
|
{"web.app", "Firebase", "firebase.google.com"},
|
||||||
|
{"appspot.com", "Google App Engine", "cloud.google.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// asnProviders: ip-api org/AS strings → hosting vendor.
|
||||||
|
var asnProviders = []provMatch{
|
||||||
|
{"cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
|
{"akamai", "Akamai", "akamai.com"},
|
||||||
|
{"linode", "Akamai (Linode)", "linode.com"},
|
||||||
|
{"amazon", "Amazon AWS", "aws.amazon.com"},
|
||||||
|
{"aws", "Amazon AWS", "aws.amazon.com"},
|
||||||
|
{"google", "Google Cloud", "cloud.google.com"},
|
||||||
|
{"microsoft", "Microsoft Azure", "azure.microsoft.com"},
|
||||||
|
{"azure", "Microsoft Azure", "azure.microsoft.com"},
|
||||||
|
{"digitalocean", "DigitalOcean", "digitalocean.com"},
|
||||||
|
{"hetzner", "Hetzner", "hetzner.com"},
|
||||||
|
{"ovh", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"kimsufi", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"soyoustart", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"vultr", "Vultr", "vultr.com"},
|
||||||
|
{"choopa", "Vultr", "vultr.com"},
|
||||||
|
{"fastly", "Fastly", "fastly.com"},
|
||||||
|
{"vercel", "Vercel", "vercel.com"},
|
||||||
|
{"netlify", "Netlify", "netlify.com"},
|
||||||
|
{"github", "GitHub", "github.com"},
|
||||||
|
{"scaleway", "Scaleway", "scaleway.com"},
|
||||||
|
{"oracle", "Oracle Cloud", "oracle.com"},
|
||||||
|
{"alibaba", "Alibaba Cloud", "alibabacloud.com"},
|
||||||
|
{"tencent", "Tencent Cloud", "cloud.tencent.com"},
|
||||||
|
{"contabo", "Contabo", "contabo.com"},
|
||||||
|
{"ionos", "IONOS", "ionos.com"},
|
||||||
|
{"1&1", "IONOS", "ionos.com"},
|
||||||
|
{"shopify", "Shopify", "shopify.com"},
|
||||||
|
{"netcup", "netcup", "netcup.eu"},
|
||||||
|
{"upcloud", "UpCloud", "upcloud.com"},
|
||||||
|
{"kamatera", "Kamatera", "kamatera.com"},
|
||||||
|
{"fly.io", "Fly.io", "fly.io"},
|
||||||
|
{"render", "Render", "render.com"},
|
||||||
|
{"railway", "Railway", "railway.app"},
|
||||||
|
{"bunny", "Bunny", "bunny.net"},
|
||||||
|
{"hostinger", "Hostinger", "hostinger.com"},
|
||||||
|
{"siteground", "SiteGround", "siteground.com"},
|
||||||
|
{"godaddy", "GoDaddy", "godaddy.com"},
|
||||||
|
{"automattic", "WordPress.com", "wordpress.com"},
|
||||||
|
{"squarespace", "Squarespace", "squarespace.com"},
|
||||||
|
{"wix", "Wix", "wix.com"},
|
||||||
|
{"infomaniak", "Infomaniak", "infomaniak.com"},
|
||||||
|
{"dreamhost", "DreamHost", "dreamhost.com"},
|
||||||
|
{"liquid web", "Liquid Web", "liquidweb.com"},
|
||||||
|
{"liquidweb", "Liquid Web", "liquidweb.com"},
|
||||||
|
{"inmotion", "InMotion", "inmotionhosting.com"},
|
||||||
|
{"unified layer", "Bluehost", "bluehost.com"},
|
||||||
|
{"hostgator", "HostGator", "hostgator.com"},
|
||||||
|
{"glesys", "GleSYS", "glesys.se"},
|
||||||
|
{"exoscale", "Exoscale", "exoscale.com"},
|
||||||
|
{"incapsula", "Imperva", "imperva.com"},
|
||||||
|
{"sucuri", "Sucuri", "sucuri.net"},
|
||||||
|
{"ddos-guard", "DDoS-Guard", "ddos-guard.net"},
|
||||||
|
{"huawei", "Huawei Cloud", "huaweicloud.com"},
|
||||||
|
{"leaseweb", "Leaseweb", "leaseweb.com"},
|
||||||
|
{"aruba", "Aruba", "aruba.it"},
|
||||||
|
{"krystal", "Krystal", "krystal.uk"},
|
||||||
|
{"namecheap", "Namecheap", "namecheap.com"},
|
||||||
|
{"web-hosting.com", "Namecheap", "namecheap.com"},
|
||||||
|
{"zendesk", "Zendesk", "zendesk.com"},
|
||||||
|
{"kinsta", "Kinsta", "kinsta.com"},
|
||||||
|
{"flywheel", "Flywheel", "getflywheel.com"},
|
||||||
|
{"wp engine", "WP Engine", "wpengine.com"},
|
||||||
|
{"pantheon", "Pantheon", "pantheon.io"},
|
||||||
|
{"meta", "Meta", "meta.com"},
|
||||||
|
{"gitlab", "GitLab", "gitlab.com"},
|
||||||
|
{"stackpath", "StackPath", "stackpath.com"},
|
||||||
|
{"cdn77", "CDN77", "cdn77.com"},
|
||||||
|
{"imperva", "Imperva", "imperva.com"},
|
||||||
|
{"framer", "Framer", "framer.com"},
|
||||||
|
{"webflow", "Webflow", "webflow.com"},
|
||||||
|
{"readthedocs", "Read the Docs", "readthedocs.io"},
|
||||||
|
{"transip", "TransIP", "transip.nl"},
|
||||||
|
{"strato", "STRATO", "strato.de"},
|
||||||
|
{"all-inkl", "ALL-INKL.COM", "all-inkl.com"},
|
||||||
|
{"one.com", "One.com", "one.com"},
|
||||||
|
{"netlify", "Netlify", "netlify.com"},
|
||||||
|
{"neocities", "Neocities", "neocities.org"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var registrarProviders = []provMatch{
|
||||||
|
{"markmonitor", "MarkMonitor", "markmonitor.com"},
|
||||||
|
{"namecheap", "Namecheap", "namecheap.com"},
|
||||||
|
{"cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
|
{"godaddy", "GoDaddy", "godaddy.com"},
|
||||||
|
{"gandi", "Gandi", "gandi.net"},
|
||||||
|
{"porkbun", "Porkbun", "porkbun.com"},
|
||||||
|
{"ovh", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"name.com", "Name.com", "name.com"},
|
||||||
|
{"ionos", "IONOS", "ionos.com"},
|
||||||
|
{"1&1", "IONOS", "ionos.com"},
|
||||||
|
{"1und1", "IONOS", "ionos.com"},
|
||||||
|
{"tucows", "Tucows", "tucows.com"},
|
||||||
|
{"squarespace", "Squarespace", "squarespace.com"},
|
||||||
|
{"google", "Google Domains", "domains.google"},
|
||||||
|
{"dynadot", "Dynadot", "dynadot.com"},
|
||||||
|
{"namesilo", "NameSilo", "namesilo.com"},
|
||||||
|
{"epik", "Epik", "epik.com"},
|
||||||
|
{"hexonet", "HEXONET", "hexonet.net"},
|
||||||
|
{"1api", "HEXONET", "hexonet.net"},
|
||||||
|
{"openprovider", "Openprovider", "openprovider.com"},
|
||||||
|
{"internetx", "InterNetX", "internetx.com"},
|
||||||
|
{"nic.cz", "CZ.NIC", "nic.cz"},
|
||||||
|
{"hostinger", "Hostinger", "hostinger.com"},
|
||||||
|
{"com laude", "Com Laude", "comlaude.com"},
|
||||||
|
{"csc", "CSC", "cscdbs.com"},
|
||||||
|
{"safenames", "Safenames", "safenames.net"},
|
||||||
|
{"xinnet", "Xin Net", "xinnet.com"},
|
||||||
|
{"alibaba", "Alibaba Cloud", "alibabacloud.com"},
|
||||||
|
{"dnspod", "DNSPod", "dnspod.com"},
|
||||||
|
{"tencent", "Tencent Cloud", "cloud.tencent.com"},
|
||||||
|
{"gabia", "Gabia", "gabia.com"},
|
||||||
|
{"reg.ru", "REG.RU", "reg.ru"},
|
||||||
|
{"nic.ru", "RU-CENTER", "nic.ru"},
|
||||||
|
{"nordname", "NordName", "nordname.com"},
|
||||||
|
{"internet.bs", "Internet.bs", "internetbs.net"},
|
||||||
|
{"key-systems", "Key-Systems", "key-systems.net"},
|
||||||
|
{"united domains", "United Domains", "uniteddomains.com"},
|
||||||
|
{"united-domains", "United Domains", "uniteddomains.com"},
|
||||||
|
{"scaleway", "Scaleway", "scaleway.com"},
|
||||||
|
{"hetzner", "Hetzner", "hetzner.com"},
|
||||||
|
{"infomaniak", "Infomaniak", "infomaniak.com"},
|
||||||
|
{"one.com", "One.com", "one.com"},
|
||||||
|
{"netim", "Netim", "netim.com"},
|
||||||
|
{"inwx", "INWX", "inwx.com"},
|
||||||
|
{"internetworx", "INWX", "inwx.com"},
|
||||||
|
{"checkdomain", "checkdomain", "checkdomain.de"},
|
||||||
|
{"domaindiscount24", "domaindiscount24", "domaindiscount24.com"},
|
||||||
|
{"easyname", "easyname", "easyname.com"},
|
||||||
|
{"world4you", "World4You", "world4you.com"},
|
||||||
|
{"all-inkl", "ALL-INKL.COM", "all-inkl.com"},
|
||||||
|
{"transip", "TransIP", "transip.nl"},
|
||||||
|
{"strato", "STRATO", "strato.de"},
|
||||||
|
{"hover", "Hover", "hover.com"},
|
||||||
|
{"enom", "eNom", "enom.com"},
|
||||||
|
{"namebright", "NameBright", "namebright.com"},
|
||||||
|
{"sav.com", "Sav.com", "sav.com"},
|
||||||
|
{"turncommerce", "DropCatch", "dropcatch.com"},
|
||||||
|
{"eurodns", "EuroDNS", "eurodns.com"},
|
||||||
|
{"gransy", "Gransy", "regtons.com"},
|
||||||
|
{"101domain", "101domain", "101domain.com"},
|
||||||
|
{"iwantmyname", "iwantmyname", "iwantmyname.com"},
|
||||||
|
{"rebel", "Rebel", "rebel.ca"},
|
||||||
|
{"blacknight", "Blacknight", "blacknight.com"},
|
||||||
|
{"gname", "GNAME", "gname.com"},
|
||||||
|
{"west.cn", "West.cn", "west.cn"},
|
||||||
|
{"west263", "West.cn", "west.cn"},
|
||||||
|
{"imena.ua", "Imena.ua", "imena.ua"},
|
||||||
|
{"ukraine.com.ua", "Hosting Ukraine", "ukraine.com.ua"},
|
||||||
|
{"beget", "Beget", "beget.com"},
|
||||||
|
{"selectel", "Selectel", "selectel.ru"},
|
||||||
|
{"timeweb", "Timeweb", "timeweb.ru"},
|
||||||
|
{"papaki", "Papaki", "papaki.com"},
|
||||||
|
{"dotname", "Dotname", "dotname.co.kr"},
|
||||||
|
{"gabia", "Gabia", "gabia.com"},
|
||||||
|
{"registar-se", "Namecheap", "namecheap.com"},
|
||||||
|
{"registrar-servers", "Namecheap", "namecheap.com"},
|
||||||
|
{"namebay", "Namebay", "namebay.com"},
|
||||||
|
{"domainpeople", "DomainPeople", "domainpeople.com"},
|
||||||
|
{"networksolutions", "Network Solutions", "networksolutions.com"},
|
||||||
|
{"network solutions", "Network Solutions", "networksolutions.com"},
|
||||||
|
{"register.com", "Register.com", "register.com"},
|
||||||
|
{"webcentral", "Webcentral", "webcentral.com.au"},
|
||||||
|
{"melbourne it", "Melbourne IT", "melbourneit.com.au"},
|
||||||
|
{"corporatedomains", "CSC", "cscdbs.com"},
|
||||||
|
{"amazon registrar", "Amazon Registrar", "aws.amazon.com"},
|
||||||
|
{"nominet", "Nominet", "nominet.uk"},
|
||||||
|
{"123-reg", "123 Reg", "123-reg.co.uk"},
|
||||||
|
{"domain.com", "Domain.com", "domain.com"},
|
||||||
|
{"dotster", "Dotster", "dotster.com"},
|
||||||
|
{"joker", "Joker.com", "joker.com"},
|
||||||
|
{"variomedia", "Variomedia", "variomedia.de"},
|
||||||
|
{"goneo", "goneo", "goneo.de"},
|
||||||
|
{"manitu", "manitu", "manitu.de"},
|
||||||
|
{"hosteurope", "Host Europe", "hosteurope.de"},
|
||||||
|
{"keyweb", "Keyweb", "keyweb.de"},
|
||||||
|
{"wedos", "WEDOS", "wedos.com"},
|
||||||
|
{"active24", "Active 24", "active24.com"},
|
||||||
|
{"forpsi", "FORPSI", "forpsi.com"},
|
||||||
|
{"ignum", "IGNUM", "ignum.cz"},
|
||||||
|
{"oneprovider", "OneProvider", "oneprovider.com"},
|
||||||
|
{"reg.cz", "REG.CZ", "reg.cz"},
|
||||||
|
{"subreg", "Websupport", "websupport.sk"},
|
||||||
|
{"websupport", "Websupport", "websupport.sk"},
|
||||||
|
{"loopia", "Loopia", "loopia.se"},
|
||||||
|
{"binero", "Binero", "binero.se"},
|
||||||
|
{"one.se", "One.com", "one.com"},
|
||||||
|
{"simply.com", "Simply.com", "simply.com"},
|
||||||
|
{"domeneshop", "Domeneshop", "domeneshop.no"},
|
||||||
|
{"syse", "Syse", "syse.no"},
|
||||||
|
{"registrar.eu", "Openprovider", "openprovider.com"},
|
||||||
|
{"mondo", "Mondo International", "mondo.com"},
|
||||||
|
{"dansk.net", "Dansk.net", "dansk.net"},
|
||||||
|
{"internet invest", "Imena.ua", "imena.ua"},
|
||||||
|
{"netearth", "NetEarth One", "netearthone.com"},
|
||||||
|
{"publicdomainregistry", "PublicDomainRegistry", "publicdomainregistry.com"},
|
||||||
|
{"pdr ltd", "PublicDomainRegistry", "publicdomainregistry.com"},
|
||||||
|
{"webnames", "Webnames", "webnames.ca"},
|
||||||
|
{"rebel.ca", "Rebel", "rebel.ca"},
|
||||||
|
{"easydns", "easyDNS", "easydns.com"},
|
||||||
|
{"namespro", "Namespro", "namespro.ca"},
|
||||||
|
{"sibername", "Sibername", "sibername.com"},
|
||||||
|
{"netfirms", "Netfirms", "netfirms.com"},
|
||||||
|
{"names.co.uk", "names.co.uk", "names.co.uk"},
|
||||||
|
{"namesco", "names.co.uk", "names.co.uk"},
|
||||||
|
{"fasthosts", "Fasthosts", "fasthosts.co.uk"},
|
||||||
|
{"heart internet", "Heart Internet", "heartinternet.uk"},
|
||||||
|
{"dreamhost", "DreamHost", "dreamhost.com"},
|
||||||
|
{"pair domains", "pair Domains", "pairdomains.com"},
|
||||||
|
{"domainthenet", "Domain The Net", "domainthenet.com"},
|
||||||
|
{"domainregistry", "DomainRegistry", "domainregistry.com"},
|
||||||
|
{"moniker", "Moniker", "moniker.com"},
|
||||||
|
{"fabulous", "Fabulous", "fabulous.com"},
|
||||||
|
{"above.com", "Above.com", "above.com"},
|
||||||
|
{"united-internet", "United Internet", "united-internet.de"},
|
||||||
|
{"united internet", "United Internet", "united-internet.de"},
|
||||||
|
{"arsys", "Arsys", "arsys.es"},
|
||||||
|
{"piensa solutions", "Piensa Solutions", "piensasolutions.com"},
|
||||||
|
{"dinahosting", "Dinahosting", "dinahosting.com"},
|
||||||
|
{"cdmon", "CDmon", "cdmon.com"},
|
||||||
|
{"nominalia", "Nominalia", "nominalia.com"},
|
||||||
|
{"register.it", "Register.it", "register.it"},
|
||||||
|
{"ovh sas", "OVHcloud", "ovhcloud.com"},
|
||||||
|
{"gandi sas", "Gandi", "gandi.net"},
|
||||||
|
{"blue razor", "GoDaddy", "godaddy.com"},
|
||||||
|
{"wild west domains", "GoDaddy", "godaddy.com"},
|
||||||
|
{"starfield", "GoDaddy", "godaddy.com"},
|
||||||
|
{"nameking", "Namecheap", "namecheap.com"},
|
||||||
|
{"enom", "eNom", "enom.com"},
|
||||||
|
{"realtime register", "Realtime Register", "realtimeregister.com"},
|
||||||
|
{"rrpproxy", "RRPproxy", "rrpproxy.net"},
|
||||||
|
{"key-systems gmbh", "Key-Systems", "key-systems.net"},
|
||||||
|
{"internet domains", "Internet Domains", "internetdomains.ch"},
|
||||||
|
{"corehub", "CORE", "corehub.net"},
|
||||||
|
{"don dominio", "DonDominio", "dondominio.com"},
|
||||||
|
{"arsys internet", "Arsys", "arsys.es"},
|
||||||
|
{"key-systems", "Key-Systems", "key-systems.net"},
|
||||||
|
{"centralnic", "CentralNic", "centralnic.com"},
|
||||||
|
{"internet.bs corp", "Internet.bs", "internetbs.net"},
|
||||||
|
{"gkg.net", "GKG.NET", "gkg.net"},
|
||||||
|
{"namecheap inc", "Namecheap", "namecheap.com"},
|
||||||
|
{"namecheap, inc", "Namecheap", "namecheap.com"},
|
||||||
|
{"google llc", "Google Domains", "domains.google"},
|
||||||
|
{"squarespace domains", "Squarespace", "squarespace.com"},
|
||||||
|
{"turncommerce, inc.", "DropCatch", "dropcatch.com"},
|
||||||
|
{"amazon", "Amazon Registrar", "aws.amazon.com"},
|
||||||
|
{"name.com, inc", "Name.com", "name.com"},
|
||||||
|
{"godaddy.com, llc", "GoDaddy", "godaddy.com"},
|
||||||
|
{"tucows domains", "Tucows", "tucows.com"},
|
||||||
|
{"porkbun llc", "Porkbun", "porkbun.com"},
|
||||||
|
{"cloudflare, inc", "Cloudflare", "cloudflare.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// caProviders: issuer CN/org → certificate authority vendor.
|
||||||
|
var caProviders = []provMatch{
|
||||||
|
{"let's encrypt", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"letsencrypt", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"digicert", "DigiCert", "digicert.com"},
|
||||||
|
{"sectigo", "Sectigo", "sectigo.com"},
|
||||||
|
{"comodoca", "Sectigo", "sectigo.com"},
|
||||||
|
{"comodo", "Sectigo", "sectigo.com"},
|
||||||
|
{"globalsign", "GlobalSign", "globalsign.com"},
|
||||||
|
{"godaddy", "GoDaddy", "godaddy.com"},
|
||||||
|
{"starfield", "GoDaddy", "godaddy.com"},
|
||||||
|
{"entrust", "Entrust", "entrust.com"},
|
||||||
|
{"google trust", "Google Trust Services", "pki.goog"},
|
||||||
|
{"pki.goog", "Google Trust Services", "pki.goog"},
|
||||||
|
{"amazon", "Amazon Trust Services", "amazontrust.com"},
|
||||||
|
{"zerossl", "ZeroSSL", "zerossl.com"},
|
||||||
|
{"buypass", "Buypass", "buypass.com"},
|
||||||
|
{"ssl.com", "SSL.com", "ssl.com"},
|
||||||
|
{"actalis", "Actalis", "actalis.com"},
|
||||||
|
{"certum", "Certum", "certum.pl"},
|
||||||
|
{"harica", "HARICA", "harica.gr"},
|
||||||
|
{"swisssign", "SwissSign", "swisssign.com"},
|
||||||
|
{"cloudflare", "Cloudflare", "cloudflare.com"},
|
||||||
|
{"microsoft", "Microsoft", "microsoft.com"},
|
||||||
|
{"geotrust", "DigiCert", "digicert.com"},
|
||||||
|
{"thawte", "DigiCert", "digicert.com"},
|
||||||
|
{"rapidssl", "DigiCert", "digicert.com"},
|
||||||
|
{"usertrust", "Sectigo", "sectigo.com"},
|
||||||
|
{"certainly", "Certainly", "certainly.io"},
|
||||||
|
{"isrg", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"e1", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"r3", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"r10", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"r11", "Let's Encrypt", "letsencrypt.org"},
|
||||||
|
{"wotrus", "WoTrus", "wotrus.com"},
|
||||||
|
{"cameo", "Cameo", "cameo.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectHostProvider picks the hosting vendor: CNAME target first (most
|
||||||
|
// precise), then response headers, then the ASN/org of the primary IPv4.
|
||||||
|
func detectHostProvider(r *Result) *Provider {
|
||||||
|
if r.CNAME != "" {
|
||||||
|
if p := matchProvider(r.CNAME, hostTargets); p != nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := r.Headers
|
||||||
server := strings.ToLower(h["server"] + " " + h["x-powered-by"] + " " + h["via"])
|
server := strings.ToLower(h["server"] + " " + h["x-powered-by"] + " " + h["via"])
|
||||||
if _, ok := h["cf-ray"]; ok || strings.Contains(server, "cloudflare") {
|
switch {
|
||||||
return "Cloudflare"
|
case h["cf-ray"] != "" || strings.Contains(server, "cloudflare"):
|
||||||
|
return prov("Cloudflare", "cloudflare.com")
|
||||||
|
case h["x-vercel-id"] != "" || strings.Contains(server, "vercel"):
|
||||||
|
return prov("Vercel", "vercel.com")
|
||||||
|
case h["x-nf-request-id"] != "" || strings.Contains(server, "netlify"):
|
||||||
|
return prov("Netlify", "netlify.com")
|
||||||
|
case h["x-amz-cf-id"] != "" || strings.Contains(server, "cloudfront"):
|
||||||
|
return prov("AWS CloudFront", "aws.amazon.com")
|
||||||
|
case h["x-github-request-id"] != "" || strings.Contains(server, "github"):
|
||||||
|
return prov("GitHub Pages", "github.com")
|
||||||
|
case strings.Contains(server, "fly.io") || h["fly-request-id"] != "":
|
||||||
|
return prov("Fly.io", "fly.io")
|
||||||
|
case strings.Contains(server, "render"):
|
||||||
|
return prov("Render", "render.com")
|
||||||
|
case strings.Contains(server, "shopify"):
|
||||||
|
return prov("Shopify", "shopify.com")
|
||||||
|
case strings.Contains(server, "squarespace"):
|
||||||
|
return prov("Squarespace", "squarespace.com")
|
||||||
|
case strings.Contains(server, "wix"):
|
||||||
|
return prov("Wix", "wix.com")
|
||||||
}
|
}
|
||||||
if _, ok := h["x-vercel-id"]; ok || strings.Contains(server, "vercel") {
|
webServers := []provMatch{
|
||||||
return "Vercel"
|
{"awselb", "AWS ELB", "aws.amazon.com"},
|
||||||
|
{"gws", "Google", "google.com"},
|
||||||
|
{"kestrel", "Kestrel/.NET", "microsoft.com"},
|
||||||
|
{"cowboy", "Cowboy/Erlang", "heroku.com"},
|
||||||
}
|
}
|
||||||
if _, ok := h["x-nf-request-id"]; ok || strings.Contains(server, "netlify") {
|
if p := matchProvider(server, webServers); p != nil {
|
||||||
return "Netlify"
|
return p
|
||||||
}
|
}
|
||||||
if _, ok := h["x-amz-cf-id"]; ok || strings.Contains(server, "cloudfront") {
|
if p := matchProvider(r.HostOrg+" "+r.HostAS+" "+r.HostISP, asnProviders); p != nil {
|
||||||
return "AWS CloudFront"
|
return p
|
||||||
}
|
}
|
||||||
if _, ok := h["x-github-request-id"]; ok || strings.Contains(server, "github") {
|
if p := matchProvider(server, []provMatch{
|
||||||
return "GitHub Pages"
|
{"nginx", "nginx", "nginx.org"},
|
||||||
|
{"apache", "Apache", "apache.org"},
|
||||||
|
{"caddy", "Caddy", "caddyserver.com"},
|
||||||
|
{"litespeed", "LiteSpeed", "litespeedtech.com"},
|
||||||
|
{"openresty", "OpenResty", "openresty.org"},
|
||||||
|
{"envoy", "Envoy", "envoyproxy.io"},
|
||||||
|
}); p != nil {
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
if strings.Contains(server, "fly.io") || h["fly-request-id"] != "" {
|
return nil
|
||||||
return "Fly.io"
|
|
||||||
}
|
|
||||||
if strings.Contains(server, "render") {
|
|
||||||
return "Render"
|
|
||||||
}
|
|
||||||
table := []struct{ sub, name string }{
|
|
||||||
{"nginx", "nginx"}, {"apache", "Apache"}, {"caddy", "Caddy"},
|
|
||||||
{"litespeed", "LiteSpeed"}, {"openresty", "OpenResty"},
|
|
||||||
{"envoy", "Envoy"}, {"awselb", "AWS ELB"}, {"gws", "Google"},
|
|
||||||
{"kestrel", "Kestrel/.NET"}, {"cowboy", "Cowboy/Erlang"},
|
|
||||||
}
|
|
||||||
for _, t := range table {
|
|
||||||
if strings.Contains(server, t.sub) {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectCA(issuer string) string {
|
// txtProviders: SPF includes and domain-verification strings → vendor.
|
||||||
i := strings.ToLower(issuer)
|
var txtProviders = []provMatch{
|
||||||
table := []struct{ sub, name string }{
|
{"v=spf1 include:_spf.google.com", "Google Workspace", "google.com"},
|
||||||
{"let's encrypt", "Let's Encrypt"}, {"letsencrypt", "Let's Encrypt"},
|
{"include:spf.protonmail.ch", "Proton Mail", "proton.me"},
|
||||||
{"digicert", "DigiCert"}, {"sectigo", "Sectigo"}, {"comodoca", "Sectigo"},
|
{"include:mail.protonmail.ch", "Proton Mail", "proton.me"},
|
||||||
{"globalsign", "GlobalSign"}, {"godaddy", "GoDaddy"}, {"starfield", "GoDaddy"},
|
{"include:_spf.mx.cloudflare.net", "Cloudflare", "cloudflare.com"},
|
||||||
{"entrust", "Entrust"}, {"google trust", "Google Trust Services"},
|
{"include:servers.mcsv.net", "Mailchimp", "mailchimp.com"},
|
||||||
{"amazon", "Amazon"}, {"zerossl", "ZeroSSL"}, {"buypass", "Buypass"},
|
{"include:amazonses.com", "Amazon SES", "aws.amazon.com"},
|
||||||
{"ssl.com", "SSL.com"}, {"actalis", "Actalis"}, {"certum", "Certum"},
|
{"include:mailgun.org", "Mailgun", "mailgun.com"},
|
||||||
{"harica", "HARICA"}, {"swisssign", "SwissSign"}, {"cloudflare", "Cloudflare"},
|
{"include:sendgrid.net", "SendGrid", "sendgrid.com"},
|
||||||
{"pki.goog", "Google Trust Services"}, {"microsoft", "Microsoft"},
|
{"include:spf.protection.outlook.com", "Microsoft 365", "microsoft.com"},
|
||||||
|
{"include:spf.purelymail.com", "Purelymail", "purelymail.com"},
|
||||||
|
{"_spf.purelymail.com", "Purelymail", "purelymail.com"},
|
||||||
|
{"purelymail_ownership_proof", "Purelymail", "purelymail.com"},
|
||||||
|
{"openai-domain-verification", "OpenAI", "openai.com"},
|
||||||
|
{"include:_spf.migadu.com", "Migadu", "migadu.com"},
|
||||||
|
{"include:mx.zohomail.com", "Zoho Mail", "zoho.com"},
|
||||||
|
{"include:_spf.fastmail.com", "Fastmail", "fastmail.com"},
|
||||||
|
{"include:spf.mtasv.net", "Postmark", "postmarkapp.com"},
|
||||||
|
{"include:_spf.salesforce.com", "Salesforce", "salesforce.com"},
|
||||||
|
{"include:spf.improvmx.com", "ImprovMX", "improvmx.com"},
|
||||||
|
{"include:mail.zendesk.com", "Zendesk", "zendesk.com"},
|
||||||
|
{"include:spf.mandrillapp.com", "Mailchimp", "mailchimp.com"},
|
||||||
|
{"include:mailspike.net", "Mailspike", "mailspike.net"},
|
||||||
|
{"include:spf.messagelabs.com", "Broadcom", "broadcom.com"},
|
||||||
|
{"include:_spf.mx.yandex", "Yandex", "yandex.com"},
|
||||||
|
{"include:spf.tuta.io", "Tuta", "tuta.com"},
|
||||||
|
{"google-site-verification", "Google", "google.com"},
|
||||||
|
{"ms=", "Microsoft 365", "microsoft.com"},
|
||||||
|
{"facebook-domain-verification", "Meta", "meta.com"},
|
||||||
|
{"apple-domain-verification", "Apple", "apple.com"},
|
||||||
|
{"keybase-site-verification", "Keybase", "keybase.io"},
|
||||||
|
{"keybase", "Keybase", "keybase.io"},
|
||||||
|
{"protonmail-verification", "Proton", "proton.me"},
|
||||||
|
{"have-i-been-pwned-verification", "Have I Been Pwned", "haveibeenpwned.com"},
|
||||||
|
{"atlassian-domain-verification", "Atlassian", "atlassian.com"},
|
||||||
|
{"docker-verification", "Docker", "docker.com"},
|
||||||
|
{"docusign", "DocuSign", "docusign.com"},
|
||||||
|
{"yandex-verification", "Yandex", "yandex.com"},
|
||||||
|
{"stripe-verification", "Stripe", "stripe.com"},
|
||||||
|
{"dropbox-domain-verification", "Dropbox", "dropbox.com"},
|
||||||
|
{"brave-ledger-verification", "Brave", "brave.com"},
|
||||||
|
{"detectify-verification", "Detectify", "detectify.com"},
|
||||||
|
{"adobe-idp-site-verification", "Adobe", "adobe.com"},
|
||||||
|
{"webexdomainverification", "Cisco Webex", "webex.com"},
|
||||||
|
{"zoho-verification", "Zoho", "zoho.com"},
|
||||||
|
{"mailru-verification", "Mail.ru", "mail.ru"},
|
||||||
|
{"baidu-site-verification", "Baidu", "baidu.com"},
|
||||||
|
{"google-gws-recovery-domain-verification", "Google", "google.com"},
|
||||||
|
{"status-page-domain-verification", "Atlassian", "atlassian.com"},
|
||||||
|
{"citrix-verification", "Citrix", "citrix.com"},
|
||||||
|
{"tiktok-domain-verification", "TikTok", "tiktok.com"},
|
||||||
|
{"netlify-verification", "Netlify", "netlify.com"},
|
||||||
|
{"vercel-domain-verification", "Vercel", "vercel.com"},
|
||||||
|
{"workplace-domain-verification", "Meta", "meta.com"},
|
||||||
|
{"v=dmarc1", "DMARC", "dmarc.org"},
|
||||||
|
{"v=dkim1", "DKIM", "dkim.org"},
|
||||||
}
|
}
|
||||||
for _, t := range table {
|
|
||||||
if strings.Contains(i, t.sub) {
|
func matchTXTProvider(txt string) *Provider {
|
||||||
return t.name
|
return matchProvider(txt, txtProviders)
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { ArrowLeft, Pause, Play, RefreshCw, Shield, Trash2 } from "lucide-react";
|
import { ArrowLeft, Globe, Pause, Play, RefreshCw, Shield, Trash2 } from "lucide-react";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import type { DomainCheck, DomainView, Subdomain } from "@/types";
|
import type { DNSRecord, DomainCheck, DomainView, Provider, Subdomain } from "@/types";
|
||||||
import { DomainDialog } from "@/components/DomainDialog";
|
import { DomainDialog } from "@/components/DomainDialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -32,6 +32,61 @@ function fmtDate(iso: string | null | undefined) {
|
|||||||
return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vendor chip shown next to records and inside the provider strip.
|
||||||
|
function ProviderBadge({ p }: { p?: Provider | null }) {
|
||||||
|
if (!p?.name) return null;
|
||||||
|
return (
|
||||||
|
<span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-border/60 bg-bg px-2 py-0.5">
|
||||||
|
{p.icon
|
||||||
|
? <img src={p.icon} alt="" className="size-3 rounded-[3px]" loading="lazy" />
|
||||||
|
: <Globe size={11} className="text-text-faint" />}
|
||||||
|
<span className="whitespace-nowrap text-[10.5px] text-text-dim">{p.name}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProvTile({ label, p }: { label: string; p?: Provider }) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-w-0 items-center gap-2.5 rounded-[9px] border border-border/60 bg-bg px-3 py-2.5">
|
||||||
|
{p?.icon
|
||||||
|
? <img src={p.icon} alt="" className="size-5 rounded-[5px]" loading="lazy" />
|
||||||
|
: <Globe size={17} className="shrink-0 text-text-faint" />}
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="font-mono text-[9px] uppercase tracking-[0.1em] text-text-faint">{label}</div>
|
||||||
|
<div className="truncate text-[12px] font-medium text-text">{p?.name || "—"}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recTypeColor = {
|
||||||
|
A: "text-[var(--chart-1)]",
|
||||||
|
AAAA: "text-[var(--chart-1)]",
|
||||||
|
CNAME: "text-[var(--chart-4)]",
|
||||||
|
NS: "text-[var(--chart-2)]",
|
||||||
|
MX: "text-[var(--chart-3)]",
|
||||||
|
TXT: "text-[var(--chart-5)]",
|
||||||
|
} satisfies Record<NonNullable<DNSRecord["type"]>, string>;
|
||||||
|
const recOrder = ["A", "AAAA", "CNAME", "NS", "MX", "TXT"];
|
||||||
|
|
||||||
|
function RecordRow({ r }: { r: DNSRecord }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 border-b border-border/50 px-4 py-2 last:border-0">
|
||||||
|
<span className={cn("w-11 shrink-0 font-mono text-[9.5px] font-semibold uppercase tracking-[0.1em]",
|
||||||
|
(r.type ? recTypeColor[r.type] : undefined) ?? "text-text-faint")}>
|
||||||
|
{r.type}
|
||||||
|
</span>
|
||||||
|
<span className="min-w-0 flex-1 truncate font-mono text-[11px] text-text" title={r.value ?? ""}>
|
||||||
|
{r.value}
|
||||||
|
{r.type === "MX" && r.priority !== null && r.priority !== undefined
|
||||||
|
? <span className="text-text-faint"> (pri {r.priority})</span>
|
||||||
|
: null}
|
||||||
|
</span>
|
||||||
|
<ProviderBadge p={r.provider} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function DomainDetailPage({ id }: { id: string }) {
|
export function DomainDetailPage({ id }: { id: string }) {
|
||||||
const [d, setD] = useState<DomainView | null>(null);
|
const [d, setD] = useState<DomainView | null>(null);
|
||||||
const [checks, setChecks] = useState<DomainCheck[]>([]);
|
const [checks, setChecks] = useState<DomainCheck[]>([]);
|
||||||
@@ -133,13 +188,26 @@ export function DomainDetailPage({ id }: { id: string }) {
|
|||||||
<div className="font-mono text-[10px] text-text-faint">{d.caProvider || "—"}</div>
|
<div className="font-mono text-[10px] text-text-faint">{d.caProvider || "—"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-[10px] border border-border bg-surface px-4 py-3">
|
<div className="rounded-[10px] border border-border bg-surface px-4 py-3">
|
||||||
<div className="font-mono text-[10px] uppercase tracking-[0.1em] text-text-faint">{t("domains.providers")}</div>
|
<div className="font-mono text-[10px] uppercase tracking-[0.1em] text-text-faint">{t("domains.checked")}</div>
|
||||||
<div className="mt-1 truncate text-[13px] font-medium">{d.dnsProvider || "—"}</div>
|
<div className="mt-1 truncate text-[13px] font-medium">
|
||||||
<div className="font-mono text-[10px] text-text-faint">{d.hostingProvider || d.emailProvider || "—"}</div>
|
{d.lastChecked ? new Date(d.lastChecked).toLocaleDateString() : "—"}
|
||||||
|
</div>
|
||||||
|
<div className="font-mono text-[10px] text-text-faint">every {d.intervalH}h</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 max-[820px]:grid-cols-1">
|
{/* Vendor strip — registrar, DNS, hosting, email, CA */}
|
||||||
|
<Section title={t("domains.providers")}>
|
||||||
|
<div className="grid grid-cols-5 gap-2 p-2.5 max-[900px]:grid-cols-3 max-[560px]:grid-cols-2">
|
||||||
|
<ProvTile label={t("domains.registrar")} p={d.providers?.registrar} />
|
||||||
|
<ProvTile label={t("domains.dns")} p={d.providers?.dns} />
|
||||||
|
<ProvTile label={t("domains.hosting")} p={d.providers?.hosting} />
|
||||||
|
<ProvTile label={t("domains.email")} p={d.providers?.email} />
|
||||||
|
<ProvTile label={t("domains.tlsCert")} p={d.providers?.ca} />
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<div className="mt-3 grid grid-cols-2 gap-3 max-[820px]:grid-cols-1">
|
||||||
<Section title={t("domains.registration")}>
|
<Section title={t("domains.registration")}>
|
||||||
<Row k={t("domains.created")} v={fmtDate(d.creationDate)} />
|
<Row k={t("domains.created")} v={fmtDate(d.creationDate)} />
|
||||||
<Row k={t("domains.updated")} v={fmtDate(d.updatedDate)} />
|
<Row k={t("domains.updated")} v={fmtDate(d.updatedDate)} />
|
||||||
@@ -153,19 +221,17 @@ export function DomainDetailPage({ id }: { id: string }) {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={t("domains.dns")}>
|
<Section title={t("domains.dns")}>
|
||||||
{d.nameServers?.map((n) => <Row key={n} k="NS" v={n} mono />)}
|
{[...(d.records ?? [])]
|
||||||
{d.mxRecords?.map((m) => <Row key={m} k="MX" v={m} mono />)}
|
.sort((a, b) => recOrder.indexOf(a.type ?? "") - recOrder.indexOf(b.type ?? ""))
|
||||||
{d.ipv4?.map((a) => <Row key={a} k="A" v={a} mono />)}
|
.map((r, i) => <RecordRow key={i} r={r} />)}
|
||||||
{d.ipv6?.map((a) => <Row key={a} k="AAAA" v={a} mono />)}
|
{(d.records ?? []).length === 0 ? (
|
||||||
{d.cname ? <Row k="CNAME" v={d.cname} mono /> : null}
|
<p className="px-4 py-4 text-center text-[12px] text-text-faint">—</p>
|
||||||
{d.txtRecords?.slice(0, 4).map((t, i) => <Row key={i} k="TXT" v={t.length > 60 ? t.slice(0, 60) + "…" : t} mono />)}
|
) : null}
|
||||||
{d.dnsProvider ? <Row k={t("domains.dnsProvider")} v={d.dnsProvider} /> : null}
|
|
||||||
{d.emailProvider ? <Row k={t("domains.email")} v={d.emailProvider} /> : null}
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title={t("domains.tlsCert")}>
|
<Section title={t("domains.tlsCert")}>
|
||||||
<Row k={t("domains.issuer")} v={d.sslIssuer} />
|
<Row k={t("domains.issuer")} v={d.sslIssuer} />
|
||||||
<Row k="CA" v={d.caProvider} />
|
<Row k="CA" v={d.providers?.ca ? <ProviderBadge p={d.providers.ca} /> : d.caProvider} />
|
||||||
<Row k={t("domains.valid")} v={`${fmtDate(d.sslValidFrom)} → ${fmtDate(d.sslValidTo)}`} mono />
|
<Row k={t("domains.valid")} v={`${fmtDate(d.sslValidFrom)} → ${fmtDate(d.sslValidTo)}`} mono />
|
||||||
<Row k={t("domains.subject")} v={d.sslSubject} mono />
|
<Row k={t("domains.subject")} v={d.sslSubject} mono />
|
||||||
<Row k={t("domains.key")} v={d.sslKeySize ? `${d.sslKeySize} bit` : ""} mono />
|
<Row k={t("domains.key")} v={d.sslKeySize ? `${d.sslKeySize} bit` : ""} mono />
|
||||||
@@ -181,7 +247,7 @@ export function DomainDetailPage({ id }: { id: string }) {
|
|||||||
<Row k={t("domains.isp")} v={d.hostIsp} />
|
<Row k={t("domains.isp")} v={d.hostIsp} />
|
||||||
<Row k={t("domains.org")} v={d.hostOrg} />
|
<Row k={t("domains.org")} v={d.hostOrg} />
|
||||||
<Row k="AS" v={d.hostAs} mono />
|
<Row k="AS" v={d.hostAs} mono />
|
||||||
<Row k={t("domains.hosting")} v={d.hostingProvider} />
|
<Row k={t("domains.hosting")} v={d.providers?.hosting ? <ProviderBadge p={d.providers.hosting} /> : d.hostingProvider} />
|
||||||
<Row k={t("domains.checked")} v={d.lastChecked ? new Date(d.lastChecked).toLocaleString() : "—"} mono />
|
<Row k={t("domains.checked")} v={d.lastChecked ? new Date(d.lastChecked).toLocaleString() : "—"} mono />
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export type Heartbeat = components["schemas"]["Heartbeat"];
|
|||||||
export type Domain = components["schemas"]["Domain"];
|
export type Domain = components["schemas"]["Domain"];
|
||||||
export type DomainInput = components["schemas"]["DomainInput"];
|
export type DomainInput = components["schemas"]["DomainInput"];
|
||||||
export type DomainView = components["schemas"]["DomainView"];
|
export type DomainView = components["schemas"]["DomainView"];
|
||||||
|
export type DNSRecord = components["schemas"]["DNSRecord"];
|
||||||
|
export type Provider = components["schemas"]["Provider"];
|
||||||
|
export type Providers = components["schemas"]["Providers"];
|
||||||
export type DomainCheck = components["schemas"]["DomainCheck"];
|
export type DomainCheck = components["schemas"]["DomainCheck"];
|
||||||
export type Subdomain = components["schemas"]["Subdomain"];
|
export type Subdomain = components["schemas"]["Subdomain"];
|
||||||
export type System = components["schemas"]["System"];
|
export type System = components["schemas"]["System"];
|
||||||
|
|||||||
@@ -1765,6 +1765,12 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
caProvider:
|
caProvider:
|
||||||
type: string
|
type: string
|
||||||
|
records:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/DNSRecord"
|
||||||
|
providers:
|
||||||
|
$ref: "#/components/schemas/Providers"
|
||||||
headers:
|
headers:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
@@ -1807,6 +1813,51 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
alerts:
|
alerts:
|
||||||
$ref: "#/components/schemas/AlertRules"
|
$ref: "#/components/schemas/AlertRules"
|
||||||
|
Provider:
|
||||||
|
type: object
|
||||||
|
description: Detected vendor behind a record or role
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
domain:
|
||||||
|
type: string
|
||||||
|
description: brand domain the favicon icon was derived from
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
Providers:
|
||||||
|
type: object
|
||||||
|
description: Detected vendor strip — registrar, DNS, hosting, email, CA
|
||||||
|
properties:
|
||||||
|
registrar:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
|
dns:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
|
email:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
|
hosting:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
|
ca:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
|
DNSRecord:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [A, AAAA, CNAME, NS, MX, TXT]
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
priority:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
description: MX preference
|
||||||
|
provider:
|
||||||
|
$ref: "#/components/schemas/Provider"
|
||||||
|
nullable: true
|
||||||
DomainView:
|
DomainView:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/Domain"
|
- $ref: "#/components/schemas/Domain"
|
||||||
|
|||||||
Reference in new issue
Block a user