mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
updated tenant filter
This commit is contained in:
@@ -1 +1 @@
|
||||
721
|
||||
722
|
||||
|
||||
@@ -76,6 +76,7 @@ interface SessionTableProps {
|
||||
onTenantIdFilterChange: (value: string) => void;
|
||||
onTenantIdFilterSubmit: () => void;
|
||||
onTenantIdFilterClear: () => void;
|
||||
tenantList: { tenantId: string; domainName: string }[];
|
||||
blockedDevicesSet: Set<string>;
|
||||
isPreviewBlocked: boolean;
|
||||
user: { isGlobalAdmin?: boolean } | null;
|
||||
@@ -112,6 +113,7 @@ export function SessionTable({
|
||||
onTenantIdFilterChange,
|
||||
onTenantIdFilterSubmit,
|
||||
onTenantIdFilterClear,
|
||||
tenantList,
|
||||
blockedDevicesSet,
|
||||
isPreviewBlocked,
|
||||
user,
|
||||
@@ -126,6 +128,18 @@ export function SessionTable({
|
||||
const [openFilterColumn, setOpenFilterColumn] = useState<string | null>(null);
|
||||
const columnSelectorRef = useRef<HTMLDivElement>(null);
|
||||
const filterDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const tenantDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [showTenantSuggestions, setShowTenantSuggestions] = useState(false);
|
||||
const [tenantSelectedIndex, setTenantSelectedIndex] = useState(-1);
|
||||
|
||||
// Fuzzy-match tenants by domain name or tenant ID
|
||||
const tenantSuggestions = (() => {
|
||||
const q = tenantIdFilter.trim().toLowerCase();
|
||||
if (q.length < 2 || tenantList.length === 0) return [];
|
||||
return tenantList
|
||||
.filter((t) => t.domainName.toLowerCase().includes(q) || t.tenantId.toLowerCase().includes(q))
|
||||
.slice(0, 8);
|
||||
})();
|
||||
|
||||
// Persist visible columns to localStorage
|
||||
useEffect(() => {
|
||||
@@ -141,12 +155,15 @@ export function SessionTable({
|
||||
if (filterDropdownRef.current && !filterDropdownRef.current.contains(e.target as Node)) {
|
||||
setOpenFilterColumn(null);
|
||||
}
|
||||
if (tenantDropdownRef.current && !tenantDropdownRef.current.contains(e.target as Node)) {
|
||||
setShowTenantSuggestions(false);
|
||||
}
|
||||
}
|
||||
if (showColumnSelector || openFilterColumn) {
|
||||
if (showColumnSelector || openFilterColumn || showTenantSuggestions) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
}, [showColumnSelector, openFilterColumn]);
|
||||
}, [showColumnSelector, openFilterColumn, showTenantSuggestions]);
|
||||
|
||||
// Compute unique values for a filterable field from all sessions (not filtered)
|
||||
const getUniqueValues = (field: keyof Session): string[] => {
|
||||
@@ -283,14 +300,46 @@ export function SessionTable({
|
||||
{/* Tenant ID Filter (Global Admin only) */}
|
||||
{globalAdminMode && (
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<div className="relative flex-1" ref={tenantDropdownRef}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter by Tenant ID (paste a GUID and press Enter)"
|
||||
placeholder="Filter by Tenant ID or domain name"
|
||||
value={tenantIdFilter}
|
||||
onChange={(e) => onTenantIdFilterChange(e.target.value)}
|
||||
onChange={(e) => {
|
||||
onTenantIdFilterChange(e.target.value);
|
||||
setShowTenantSuggestions(true);
|
||||
setTenantSelectedIndex(-1);
|
||||
}}
|
||||
onFocus={() => {
|
||||
if (tenantSuggestions.length > 0) setShowTenantSuggestions(true);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (showTenantSuggestions && tenantSuggestions.length > 0) {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setTenantSelectedIndex((i) => Math.min(i + 1, tenantSuggestions.length - 1));
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setTenantSelectedIndex((i) => Math.max(i - 1, -1));
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" && tenantSelectedIndex >= 0) {
|
||||
e.preventDefault();
|
||||
const selected = tenantSuggestions[tenantSelectedIndex];
|
||||
onTenantIdFilterChange(selected.tenantId);
|
||||
setShowTenantSuggestions(false);
|
||||
setTenantSelectedIndex(-1);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setShowTenantSuggestions(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
setShowTenantSuggestions(false);
|
||||
onTenantIdFilterSubmit();
|
||||
}
|
||||
}}
|
||||
@@ -298,7 +347,10 @@ export function SessionTable({
|
||||
/>
|
||||
{tenantIdFilter && (
|
||||
<button
|
||||
onClick={onTenantIdFilterClear}
|
||||
onClick={() => {
|
||||
onTenantIdFilterClear();
|
||||
setShowTenantSuggestions(false);
|
||||
}}
|
||||
className="absolute right-3 top-2.5 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
title="Clear tenant filter"
|
||||
>
|
||||
@@ -307,9 +359,35 @@ export function SessionTable({
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{/* Tenant suggestions dropdown */}
|
||||
{showTenantSuggestions && tenantSuggestions.length > 0 && (
|
||||
<div className="absolute z-50 mt-1 w-full bg-white border border-purple-200 rounded-lg shadow-lg max-h-64 overflow-y-auto">
|
||||
{tenantSuggestions.map((t, idx) => (
|
||||
<button
|
||||
key={t.tenantId}
|
||||
onClick={() => {
|
||||
onTenantIdFilterChange(t.tenantId);
|
||||
setShowTenantSuggestions(false);
|
||||
setTenantSelectedIndex(-1);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2.5 flex flex-col gap-0.5 transition-colors ${
|
||||
idx === tenantSelectedIndex
|
||||
? "bg-purple-100"
|
||||
: "hover:bg-purple-50"
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm font-medium text-gray-900">{t.domainName}</span>
|
||||
<span className="text-xs text-gray-500 font-mono">{t.tenantId}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onTenantIdFilterSubmit}
|
||||
onClick={() => {
|
||||
setShowTenantSuggestions(false);
|
||||
onTenantIdFilterSubmit();
|
||||
}}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm font-medium shrink-0"
|
||||
>
|
||||
Filter
|
||||
|
||||
@@ -24,6 +24,11 @@ interface TenantConfigurationSummary {
|
||||
validateAutopilotDevice: boolean;
|
||||
}
|
||||
|
||||
interface TenantListItem {
|
||||
tenantId: string;
|
||||
domainName: string;
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const { user, logout, getAccessToken, isPreviewBlocked } = useAuth();
|
||||
@@ -37,6 +42,7 @@ export default function Home() {
|
||||
const [serialValidationEnabled, setSerialValidationEnabled] = useState<boolean | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [tenantIdFilter, setTenantIdFilter] = useState("");
|
||||
const [tenantList, setTenantList] = useState<TenantListItem[]>([]);
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||
const [sortColumn, setSortColumn] = useState<keyof Session | null>(null);
|
||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||
@@ -389,6 +395,31 @@ export default function Home() {
|
||||
fetchSessions();
|
||||
}, [globalAdminMode]);
|
||||
|
||||
// Fetch tenant list for fuzzy search when entering global admin mode
|
||||
useEffect(() => {
|
||||
if (!globalAdminMode) {
|
||||
setTenantList([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await authenticatedFetch(api.config.all(), getAccessToken);
|
||||
if (!res.ok || cancelled) return;
|
||||
const configs: { tenantId?: string; domainName?: string }[] = await res.json();
|
||||
if (cancelled) return;
|
||||
setTenantList(
|
||||
configs
|
||||
.filter((c): c is { tenantId: string; domainName: string } => !!c.tenantId && !!c.domainName)
|
||||
.map((c) => ({ tenantId: c.tenantId, domainName: c.domainName }))
|
||||
);
|
||||
} catch {
|
||||
// Non-critical — tenant autocomplete just won't work
|
||||
}
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [globalAdminMode, getAccessToken]);
|
||||
|
||||
// Reset to page 1 when search query, status filter, sort, or page size changes
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
@@ -680,6 +711,7 @@ export default function Home() {
|
||||
onTenantIdFilterChange={applyTenantIdFilter}
|
||||
onTenantIdFilterSubmit={submitTenantIdFilter}
|
||||
onTenantIdFilterClear={clearTenantIdFilter}
|
||||
tenantList={tenantList}
|
||||
blockedDevicesSet={blockedDevicesSet}
|
||||
isPreviewBlocked={isPreviewBlocked}
|
||||
user={user}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user