feat(admin): show recent address locations
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { resolveEffectiveGroup } from "@minecraft-account-manager/auth";
|
||||
import { formatManagedDiscordNickname } from "@minecraft-account-manager/minecraft";
|
||||
import { events, groups, ipObservations, minecraftAccounts, userGroupMemberships, users } from "@minecraft-account-manager/database";
|
||||
import { events, groups, ipIntelligence, ipObservations, minecraftAccounts, userGroupMemberships, users } from "@minecraft-account-manager/database";
|
||||
import { and, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { groupAccessAddresses } from "@/lib/access-address-groups";
|
||||
import { accessAddressDetails, groupAccessAddresses } from "@/lib/access-address-groups";
|
||||
import { db } from "@/lib/database";
|
||||
import { discordIdentity } from "@/lib/discord-identity";
|
||||
import { eventCategory, eventCategoryValues, normalizeEventCategory, normalizeSelectedEventTypes } from "@/lib/event-filters";
|
||||
@@ -80,8 +80,16 @@ export default async function AdminUserPage({
|
||||
.orderBy(desc(minecraftAccounts.isPrimary), minecraftAccounts.username),
|
||||
recentEventsQuery,
|
||||
db
|
||||
.select()
|
||||
.select({
|
||||
id: ipObservations.id,
|
||||
ipAddress: ipObservations.ipAddress,
|
||||
source: ipObservations.source,
|
||||
classification: ipObservations.classification,
|
||||
observedAt: ipObservations.observedAt,
|
||||
intelligence: ipIntelligence.rawResponse,
|
||||
})
|
||||
.from(ipObservations)
|
||||
.leftJoin(ipIntelligence, eq(ipIntelligence.ipAddress, ipObservations.ipAddress))
|
||||
.where(eq(ipObservations.userId, user.id))
|
||||
.orderBy(desc(ipObservations.observedAt))
|
||||
.limit(100),
|
||||
@@ -95,9 +103,7 @@ export default async function AdminUserPage({
|
||||
const explicitGroup = availableGroups.find((group) => !group.isDefault) ?? null;
|
||||
const defaultGroup = availableGroups.find((group) => group.isDefault) ?? null;
|
||||
const effectiveGroup = resolveEffectiveGroup(explicitGroup, defaultGroup);
|
||||
const addressGroups = groupAccessAddresses(
|
||||
observations.map((observation) => ({ ...observation, intelligence: null })),
|
||||
);
|
||||
const addressGroups = groupAccessAddresses(observations);
|
||||
const primary = accounts.find((account) => account.isPrimary);
|
||||
const nickname = user.firstName
|
||||
? formatManagedDiscordNickname(user.firstName, primary?.username ?? null)
|
||||
@@ -215,16 +221,20 @@ export default async function AdminUserPage({
|
||||
<p className="font-mono text-[9px] font-bold uppercase tracking-widest text-muted">Recent addresses</p>
|
||||
<p className="mt-3 text-[10px] leading-5 text-muted">Grouped by IPv4 /24 or IPv6 /64 network across the 100 most recent observations.</p>
|
||||
<div className="mt-4 divide-y divide-line">
|
||||
{addressGroups.map((group) => (
|
||||
<div className="py-3" key={group.network}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="font-mono text-xs font-bold">{group.network}</p>
|
||||
<span className="font-mono text-[9px] text-muted">×{group.count}</span>
|
||||
{addressGroups.map((group) => {
|
||||
const details = accessAddressDetails(group);
|
||||
return (
|
||||
<div className="py-3" key={group.network}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="font-mono text-xs font-bold">{group.network}</p>
|
||||
<span className="font-mono text-[9px] text-muted">×{group.count}</span>
|
||||
</div>
|
||||
<p className="mt-1 font-mono text-[9px] text-muted">{group.sources.join(" + ")} · {group.latestObservedAt.toISOString()}</p>
|
||||
<p className="mt-1 break-all font-mono text-[9px] text-muted">Latest {group.latestAddress}</p>
|
||||
<p className="mt-1 text-xs text-muted">{details.location} · <span className="font-mono uppercase">{details.classification}</span></p>
|
||||
</div>
|
||||
<p className="mt-1 font-mono text-[9px] text-muted">{group.sources.join(" + ")} · {group.latestObservedAt.toISOString()}</p>
|
||||
<p className="mt-1 break-all font-mono text-[9px] text-muted">Latest {group.latestAddress}</p>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
{!addressGroups.length && <p className="py-3 text-xs text-muted">No addresses recorded.</p>}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user