Skip to content

Commit

Permalink
configure: a bunch of UI
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Aug 8, 2024
1 parent 17928a8 commit 4bd8662
Show file tree
Hide file tree
Showing 13 changed files with 652 additions and 57 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions apps/configure/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const devices = import.meta.glob<true, "default", string>("./devices/*.png", {
import: "default",
eager: true,
});

export function determineDeviceImage() {
// TODO: Properly implement this logic
const d = Object.values(devices)[0]!;
return d;
}
31 changes: 31 additions & 0 deletions apps/configure/src/components/search/configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,37 @@ export const entities = {
},
filters: {},
}),
scripts: defineEntity({
load: async (db) => await db.getAll("scripts"),
columns: {
type: typeColumn("SCRIPT"),
name: {
header: "Name",
render: (script) => (
<A
class="font-medium hover:underline focus:underline p-1 -m-1 w-full block"
href={`../scripts/${script.id}`}
>
{script.name}
</A>
),
raw: (script) => script.name,
},
description: {
header: "Description",
render: (group) => <p>{group.description}</p>,
raw: (group) => group.description!, // TODO: Remove assert
},
},
actions: {
delete: {
title: "Delete",
variant: "destructive",
apply: async (data) => alert("TODO: Bulk delete"),
},
},
filters: {},
}),
apps: defineEntity({
load: async (db) => await db.getAll("apps"),
columns: {
Expand Down
8 changes: 8 additions & 0 deletions apps/configure/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ export const routes = [
path: "/policies/:policyId",
component: lazy(() => import("./routes/(dash)/policies/[policyId]")),
},
{
path: "/scripts",
component: lazy(() => import("./routes/(dash)/scripts/(scripts)")),
},
{
path: "/scripts/:scriptId",
component: lazy(() => import("./routes/(dash)/scripts/[scriptId]")),
},
{
path: "/applications",
component: lazy(
Expand Down
3 changes: 2 additions & 1 deletion apps/configure/src/routes/(dash).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ const items = [
{ title: "Devices", href: "devices" },
{ title: "Groups", href: "groups" },
{ title: "Policies", href: "policies" },
{ title: "Scripts", href: "scripts" },
{ title: "Applications", href: "applications" },
{ title: "Views", href: "views" },
{ title: "Settings", href: "settings" },
Expand Down Expand Up @@ -585,7 +586,7 @@ function SyncPanel() {
return (
<div class="flex justify-center items-center space-x-2">
<Suspense>
<Show when={!persistentStorageAccess() ?? false}>
<Show when={!(persistentStorageAccess() ?? false)}>
<div>
<Tooltip>
<TooltipTrigger
Expand Down
9 changes: 8 additions & 1 deletion apps/configure/src/routes/(dash)/(overview).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ export default function Page() {
devices: await db.count("devices"),
groups: await db.count("groups"),
policies: await db.count("policies"),
scripts: await db.count("scripts"),
applications: await db.count("apps"),
};
});

return (
<PageLayout heading={<PageLayoutHeading>Overview</PageLayoutHeading>}>
<div class="grid gap-4 grid-cols-5">
<div class="grid gap-4 grid-cols-6">
<StatItem
title="Users"
href="users"
Expand All @@ -34,6 +35,12 @@ export default function Page() {
icon={<IconPhScroll />}
value={counts()?.policies || 0}
/>
<StatItem
title="Scripts"
href="scripts"
icon={<IconPhTerminal />}
value={counts()?.scripts || 0}
/>
<StatItem
title="Applications"
href="applications"
Expand Down
245 changes: 239 additions & 6 deletions apps/configure/src/routes/(dash)/devices/[deviceId].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { Suspense } from "solid-js";
import {
Badge,
Button,
Card,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
buttonVariants,
} from "@mattrax/ui";
import clsx from "clsx";
import { Show, Suspense } from "solid-js";
import { z } from "zod";
import { PageLayout, PageLayoutHeading } from "~/components/PageLayout";
import { determineDeviceImage } from "~/assets";
import { PageLayout } from "~/components/PageLayout";
import { getKey } from "~/lib/kv";
import { createDbQuery } from "~/lib/query";
import { useZodParams } from "~/lib/useZodParams";
import { Field, renderDate } from "../users/[userId]";

export default function Page() {
const params = useZodParams({
Expand All @@ -12,15 +26,234 @@ export default function Page() {
const data = createDbQuery((db) => db.get("devices", params.deviceId));
// TODO: 404 handling

const org = createDbQuery((db) => getKey(db, "org"));

const entraIdLink = () => {
const orgId = org()?.id;
const deviceId = data()?.deviceId;
if (!orgId || !deviceId) return;
return `https://intune.microsoft.com/${encodeURIComponent(orgId)}#view/Microsoft_Intune_Devices/DeviceSettingsMenuBlade/~/overview/mdmDeviceId/${encodeURIComponent(deviceId)}`;
};

return (
<PageLayout
class="max-w-7xl space-y-2"
heading={
<PageLayoutHeading>
<Suspense fallback={<p>TODO: Loading...</p>}>{data()?.name}</Suspense>
</PageLayoutHeading>
<div class="flex items-center space-x-4 p-4 w-full">
<img src={determineDeviceImage()} alt="TODO" class="w-20 h-20" />

<div>
<h1 class="text-3xl font-bold">
<Suspense
fallback={
<div class="w-42 h-8 rounded-full bg-neutral-200 animate-pulse" />
}
>
{data()?.name}
</Suspense>
</h1>
<h2 class="flex items-center mt-1 opacity-80 text-sm">
{/* // TODO: Make this actually determined from the data */}
<IconLogosMicrosoftWindowsIcon class="mr-2" />

<Suspense
fallback={
<div class="w-52 h-4 rounded-full bg-neutral-200 animate-pulse" />
}
>
{data()?.model}
</Suspense>
</h2>
</div>

<div class="flex-1" />

<div class="flex space-x-4">
<a
class={clsx(
buttonVariants({ variant: "link" }),
"!p-0",
!entraIdLink() ? "cursor-default select-none" : "",
)}
target="_blank"
href={entraIdLink()}
rel="noreferrer"
>
Microsoft Entra ID
<IconPrimeExternalLink class="inline ml-1" />
</a>

<DropdownMenu>
<DropdownMenuTrigger as={Button}>
Actions
<IconPhCaretDown class="ml-1 w-3 h-3" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => alert("TODO")}>
Sync
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("TODO")}>
Reboot
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("TODO")}>
New remote assistance session
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("TODO")}>
Locate device
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("TODO")}>
Run remediation
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Remote Lock
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Reset passcode
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Collect diagnostics
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Fresh Start
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Autopilot Reset
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Quick scan
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Full scan
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Update Windows Defender security intelligence
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Rotate local admin password
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
BitLocker key rotation
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Pause config refresh
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Retire
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Wipe
</DropdownMenuItem>
<DropdownMenuItem
class="text-destructive"
onClick={() => alert("TODO")}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
}
>
<h1 class="text-muted-foreground opacity-70">Coming soon...</h1>
<Card>
<div class="p-8 grid grid-cols-4 gap-y-8 gap-x-4">
<Field label="Identifier" value={data()?.id} />
<Field label="Name" value={data()?.name} />
<Field label="Ownership" value={data()?.deviceOwnership} />

<Field label="Last Sign in Date" value={data()?.lastSignInDate} />

<Field label="Model" value={data()?.model} />
<Field label="Manufacturer" value={data()?.manufacturer} />
{/* <Field label="Serial Number" value={data()?.serialNumber} /> */}
<Field label="Model" value={data()?.model} />

{/* // TODO: Merge these two things */}
<Field label="Operating System" value={data()?.operatingSystem} />
<Field
label="Operating System Version"
value={data()?.operatingSystemVersion}
/>

<Field
label="Registration Time"
value={data()?.registrationDateTime}
render={renderDate}
/>

<Field label="Trust Type" value={data()?.trustType} />
<Field label="Type" value={data()?.type} />

<Field
label="Compliant"
value={data()?.isCompliant}
render={(v) => (
<Show when={v === false} fallback={<Badge>Compliant</Badge>}>
<Badge variant="destructive">Non-compliant</Badge>
</Show>
)}
/>
<Field
label="Managed"
value={data()?.isManaged}
render={(v) => (
<Show when={v === false} fallback={<Badge>Managed</Badge>}>
<Badge variant="destructive">Non-Managed</Badge>
</Show>
)}
/>
<Field
label="Rooted"
value={data()?.isRooted}
render={(v) => (
<Show when={v === false} fallback={<Badge>Rooted</Badge>}>
<Badge variant="destructive">Not rooted</Badge>
</Show>
)}
/>

<Field label="Device Category" value={data()?.deviceCategory} />
</div>
</Card>
</PageLayout>
);
}
18 changes: 18 additions & 0 deletions apps/configure/src/routes/(dash)/scripts/(scripts).tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PageLayout, PageLayoutHeading } from "~/components/PageLayout";
import { SearchPage, createSearchPageContext } from "~/components/search";

export default function Page() {
const ctx = createSearchPageContext([
{
type: "enum",
target: "type",
value: "scripts",
},
]);

return (
<PageLayout heading={<PageLayoutHeading>Scripts</PageLayoutHeading>}>
<SearchPage {...ctx} showFilterBar={false} />
</PageLayout>
);
}
Loading

0 comments on commit 4bd8662

Please sign in to comment.