143 lines
5.9 KiB
TypeScript
143 lines
5.9 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { AppWindow, Box, ShieldCheck, Zap } from "lucide-react";
|
|
import { appopsClient } from "@/api/appopsClient";
|
|
import {
|
|
CommandDialog,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
CommandSeparator,
|
|
CommandShortcut,
|
|
} from "@/components/ui/command";
|
|
import { buildCommandPaletteGroups } from "@/lib/commandPaletteNavigation";
|
|
import { isCommandPaletteShortcut } from "@/lib/commandPaletteShortcut";
|
|
import { navigationGroups } from "@/lib/navigationGroups";
|
|
|
|
const commandPaletteGroups = buildCommandPaletteGroups(navigationGroups);
|
|
|
|
export function CommandPalette() {
|
|
const [open, setOpen] = React.useState(false);
|
|
const [liveItems, setLiveItems] = React.useState<{
|
|
apps: Awaited<ReturnType<typeof appopsClient.getApps>>;
|
|
containers: Awaited<ReturnType<typeof appopsClient.getContainers>>;
|
|
policies: Awaited<ReturnType<typeof appopsClient.getPolicies>>;
|
|
actions: Awaited<ReturnType<typeof appopsClient.getActionRegistry>>["items"];
|
|
}>({ apps: [], containers: [], policies: [], actions: [] });
|
|
const navigate = useNavigate();
|
|
|
|
React.useEffect(() => {
|
|
const down = (e: KeyboardEvent) => {
|
|
if (isCommandPaletteShortcut(e)) {
|
|
e.preventDefault();
|
|
setOpen((open) => !open);
|
|
}
|
|
};
|
|
document.addEventListener("keydown", down);
|
|
return () => document.removeEventListener("keydown", down);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (!open) return;
|
|
let active = true;
|
|
Promise.allSettled([
|
|
appopsClient.getApps(),
|
|
appopsClient.getContainers(),
|
|
appopsClient.getPolicies(),
|
|
appopsClient.getActionRegistry(),
|
|
]).then(([apps, containers, policies, actions]) => {
|
|
if (!active) return;
|
|
setLiveItems({
|
|
apps: apps.status === "fulfilled" ? apps.value : [],
|
|
containers: containers.status === "fulfilled" ? containers.value : [],
|
|
policies: policies.status === "fulfilled" ? policies.value : [],
|
|
actions: actions.status === "fulfilled" ? actions.value.items : [],
|
|
});
|
|
});
|
|
return () => { active = false; };
|
|
}, [open]);
|
|
|
|
const runCommand = (command: () => void) => {
|
|
setOpen(false);
|
|
command();
|
|
};
|
|
|
|
return (
|
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
<CommandInput placeholder="Typ een commando of zoek..." className="border-none focus:ring-0" />
|
|
<CommandList className="bg-slate-950 text-slate-200 border-t border-slate-800">
|
|
<CommandEmpty>Geen resultaten gevonden.</CommandEmpty>
|
|
{commandPaletteGroups.map((group, groupIndex) => (
|
|
<React.Fragment key={group.label}>
|
|
{groupIndex > 0 && <CommandSeparator className="bg-slate-800" />}
|
|
<CommandGroup heading={group.label}>
|
|
{group.items.map((item) => (
|
|
<CommandItem
|
|
key={item.path}
|
|
value={`${item.searchLabel} ${group.label} ${item.path}`}
|
|
onSelect={() => runCommand(() => navigate(item.path))}
|
|
>
|
|
<item.icon className="mr-2 h-4 w-4" />
|
|
<span>{item.searchLabel}</span>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</React.Fragment>
|
|
))}
|
|
{liveItems.apps.length > 0 && (
|
|
<>
|
|
<CommandSeparator className="bg-slate-800" />
|
|
<CommandGroup heading="Apps">
|
|
{liveItems.apps.map((app) => (
|
|
<CommandItem key={`app-${app.id}`} value={`${app.name} ${app.id} ${app.category} app`} onSelect={() => runCommand(() => navigate(`/apps/${encodeURIComponent(app.id)}`))}>
|
|
<AppWindow className="mr-2 h-4 w-4" /><span>{app.name}</span><CommandShortcut>{app.status}</CommandShortcut>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</>
|
|
)}
|
|
{liveItems.containers.length > 0 && (
|
|
<>
|
|
<CommandSeparator className="bg-slate-800" />
|
|
<CommandGroup heading="Containers">
|
|
{liveItems.containers.map((container) => (
|
|
<CommandItem key={`container-${container.id}`} value={`${container.name} ${container.image} ${container.appId || ""} container`} onSelect={() => runCommand(() => navigate(`/containers?query=${encodeURIComponent(container.name)}`))}>
|
|
<Box className="mr-2 h-4 w-4" /><span>{container.name}</span><CommandShortcut>{container.state}</CommandShortcut>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</>
|
|
)}
|
|
{liveItems.actions.length > 0 && (
|
|
<>
|
|
<CommandSeparator className="bg-slate-800" />
|
|
<CommandGroup heading="Acties">
|
|
{liveItems.actions.map((action) => (
|
|
<CommandItem key={`action-${action.id}-${action.target}`} value={`${action.label} ${action.id} ${action.target} ${action.category}`} onSelect={() => runCommand(() => navigate(`/command-center?query=${encodeURIComponent(action.id)}`))}>
|
|
<Zap className="mr-2 h-4 w-4" /><span>{action.label}</span><CommandShortcut>{action.policyDecision}</CommandShortcut>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</>
|
|
)}
|
|
{liveItems.policies.length > 0 && (
|
|
<>
|
|
<CommandSeparator className="bg-slate-800" />
|
|
<CommandGroup heading="Policies">
|
|
{liveItems.policies.map((policy) => (
|
|
<CommandItem key={`policy-${policy.id}`} value={`${policy.action} ${policy.target} ${policy.reason || ""} policy`} onSelect={() => runCommand(() => navigate(`/policies?query=${encodeURIComponent(policy.id)}`))}>
|
|
<ShieldCheck className="mr-2 h-4 w-4" /><span>{policy.action}</span><CommandShortcut>{policy.manual}</CommandShortcut>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</>
|
|
)}
|
|
</CommandList>
|
|
</CommandDialog>
|
|
);
|
|
}
|